From 4ce1a90038b23d931858ad22815dbe29f74d7d98 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Fri, 13 Apr 2012 17:06:09 -0500 Subject: [PATCH 01/78] add Git Data API support --- pygithub3/github.py | 9 ++ pygithub3/requests/git_data/__init__.py | 0 pygithub3/requests/git_data/blobs.py | 20 +++ pygithub3/requests/git_data/commits.py | 20 +++ pygithub3/requests/git_data/references.py | 33 +++++ pygithub3/requests/git_data/tags.py | 16 +++ pygithub3/requests/git_data/trees.py | 20 +++ pygithub3/resources/git_data.py | 27 ++++ pygithub3/services/git_data/__init__.py | 20 +++ pygithub3/services/git_data/blobs.py | 24 ++++ pygithub3/services/git_data/commits.py | 29 ++++ pygithub3/services/git_data/references.py | 67 +++++++++ pygithub3/services/git_data/tags.py | 34 +++++ pygithub3/services/git_data/trees.py | 34 +++++ pygithub3/tests/services/test_git_data.py | 164 ++++++++++++++++++++++ 15 files changed, 517 insertions(+) create mode 100644 pygithub3/requests/git_data/__init__.py create mode 100644 pygithub3/requests/git_data/blobs.py create mode 100644 pygithub3/requests/git_data/commits.py create mode 100644 pygithub3/requests/git_data/references.py create mode 100644 pygithub3/requests/git_data/tags.py create mode 100644 pygithub3/requests/git_data/trees.py create mode 100644 pygithub3/resources/git_data.py create mode 100644 pygithub3/services/git_data/__init__.py create mode 100644 pygithub3/services/git_data/blobs.py create mode 100644 pygithub3/services/git_data/commits.py create mode 100644 pygithub3/services/git_data/references.py create mode 100644 pygithub3/services/git_data/tags.py create mode 100644 pygithub3/services/git_data/trees.py create mode 100644 pygithub3/tests/services/test_git_data.py diff --git a/pygithub3/github.py b/pygithub3/github.py index 0b302a1..13829a0 100644 --- a/pygithub3/github.py +++ b/pygithub3/github.py @@ -17,9 +17,11 @@ def __init__(self, **config): from pygithub3.services.users import User from pygithub3.services.repos import Repo from pygithub3.services.gists import Gist + from pygithub3.services.git_data import GitData self._users = User(**config) self._repos = Repo(**config) self._gists = Gist(**config) + self._git_data = GitData(**config) @property def remaining_requests(self): @@ -47,3 +49,10 @@ def gists(self): :ref:`Gists service ` """ return self._gists + + @property + def git_data(self): + """ + :ref:`Git Data service ` + """ + return self._git_data diff --git a/pygithub3/requests/git_data/__init__.py b/pygithub3/requests/git_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pygithub3/requests/git_data/blobs.py b/pygithub3/requests/git_data/blobs.py new file mode 100644 index 0000000..4a49c6b --- /dev/null +++ b/pygithub3/requests/git_data/blobs.py @@ -0,0 +1,20 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Blob + + +class Get(Request): + + uri = 'repos/{user}/{repo}/git/blobs/{sha}' + resource = Blob + + +class Create(Request): + + uri = 'repos/{user}/{repo}/git/blobs' + resource = Blob + body_schema = { + 'schema': ('content', 'encoding'), + 'required': ('content', 'encoding'), #TODO: is enc really required? + } diff --git a/pygithub3/requests/git_data/commits.py b/pygithub3/requests/git_data/commits.py new file mode 100644 index 0000000..936d3c7 --- /dev/null +++ b/pygithub3/requests/git_data/commits.py @@ -0,0 +1,20 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Commit + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/commits/{sha}' + resource = Commit + + +class Create(Request): + uri = 'repos/{user}/{repo}/git/commits' + resource = Commit + body_schema = { + 'schema': ('message', 'tree', 'parents', 'author', 'committer'), + 'required': ('message', 'tree', 'parents'), + } + + diff --git a/pygithub3/requests/git_data/references.py b/pygithub3/requests/git_data/references.py new file mode 100644 index 0000000..99cf41a --- /dev/null +++ b/pygithub3/requests/git_data/references.py @@ -0,0 +1,33 @@ +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Reference + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/refs/{ref}' + resource = Reference + + +class List(Request): + uri = 'repos/{user}/{repo}/git/refs' + resource = Reference + + +class Create(Request): + uri = 'repos/{user}/{repo}/git/refs' + resource = Reference + body_schema = { + 'schema': ('ref', 'sha'), + 'required': ('ref', 'sha'), + } + +class Update(Request): + uri = 'repos/{user}/{repo}/git/refs/{ref}' + resource = Reference + body_schema = { + 'schema': ('sha', 'force'), + 'required': ('sha',), + } + + +class Delete(Request): + uri = 'repos/{user}/{repo}/git/refs/{ref}' diff --git a/pygithub3/requests/git_data/tags.py b/pygithub3/requests/git_data/tags.py new file mode 100644 index 0000000..8b37f0e --- /dev/null +++ b/pygithub3/requests/git_data/tags.py @@ -0,0 +1,16 @@ +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Tag + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/tags/{sha}' + resource = Tag + +class Create(Request): + uri = 'repos/{user}/{repo}/git/tags' + resource = Tag + body_schema = { + 'schema': ('tag', 'message', 'object', 'type', 'tagger'), + 'required': ('type',), + } + diff --git a/pygithub3/requests/git_data/trees.py b/pygithub3/requests/git_data/trees.py new file mode 100644 index 0000000..bd1593f --- /dev/null +++ b/pygithub3/requests/git_data/trees.py @@ -0,0 +1,20 @@ +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Tree + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/trees/{sha}' + resource = Tree + + def clean_uri(self): + if self.recursive: + return self.uri + '?recursive=1' + + +class Create(Request): + uri = 'repos/{user}/{repo}/git/trees' + resource = Tree + body_schema = { + 'schema': ('tree',), + 'required': ('tree',), + } diff --git a/pygithub3/resources/git_data.py b/pygithub3/resources/git_data.py new file mode 100644 index 0000000..4adcf5d --- /dev/null +++ b/pygithub3/resources/git_data.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from .base import Resource +from .repos import Author, Commit + + +class Blob(Resource): + def __str__(self): + return "" % getattr(self, 'content', '') + + +class Reference(Resource): + def __str__(self): + return '' % getattr(self, 'ref', '') + + +class Tag(Resource): + _maps = {'object': Commit, + 'tagger': Author,} # committer? tagger? + def __str__(self): + return '' % getattr(self, 'tag', '') + + +class Tree(Resource): + def __str__(self): + return '' % getattr(self, 'sha', '') diff --git a/pygithub3/services/git_data/__init__.py b/pygithub3/services/git_data/__init__.py new file mode 100644 index 0000000..a12ddea --- /dev/null +++ b/pygithub3/services/git_data/__init__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service +from .blobs import Blobs +from .commits import Commits +from .references import References +from .tags import Tags +from .trees import Trees + +def GitData(Service): + """Consume `Git Data API `_""" + + def __init__(self, **config): + self.blobs = Blobs(**config) + self.commits = Commits(**config) + self.references = References(**config) + self.tags = Tags(**config) + self.trees = Trees(**config) + super(GitData, self).__init__(**config) diff --git a/pygithub3/services/git_data/blobs.py b/pygithub3/services/git_data/blobs.py new file mode 100644 index 0000000..d5baa2d --- /dev/null +++ b/pygithub3/services/git_data/blobs.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service + + +class Blobs(Service): + """Consume `Blobs API `_""" + + def get(self, sha, user=None, repo=None): + """Get a particular blob + + :param str sha: The sha of the blob to get + + """ + request = self.make_request('git_data.blobs.get', sha=sha, + user=user, repo=repo) + return self._get(request) + + def create(self, data, user=None, repo=None): + """Create a blob""" + request = self.make_request('git_data.blobs.create', body=data, + user=user, repo=repo) + return self._post(request) diff --git a/pygithub3/services/git_data/commits.py b/pygithub3/services/git_data/commits.py new file mode 100644 index 0000000..cdca300 --- /dev/null +++ b/pygithub3/services/git_data/commits.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service + + +class Commits(Service): + """Consume `Commits API `_""" + + def get(self, sha, user=None, repo=None): + """get a commit from the current repo""" + request = self.make_request('git_data.commits.get', sha=sha, + user=user, repo=repo) + return self._get(request) + + def create(self, data, user=None, repo=None): + """create a commit on a repo + + :param dict data: Input. See `github commits doc`_ + :param str user: username + :param str repo: repository name + + """ + return self._post( + self.make_request('git_data.commits.create', user=user, repo=repo, + body=data) + ) + + diff --git a/pygithub3/services/git_data/references.py b/pygithub3/services/git_data/references.py new file mode 100644 index 0000000..8ae0865 --- /dev/null +++ b/pygithub3/services/git_data/references.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service + + +class References(Service): + """Consume `References API `_""" + + def get(self, ref, user=None, repo=None): + """Get a reference. + + .. note:: + Remember that branch references look like "heads/" + + """ + return self._get( + self.make_request('git_data.references.get', ref=ref, user=user, + repo=repo) + ) + + def list(self, namespace='', user=None, repo=None): + """List all the references + + :param str namespace: Limit the request to a particular type of + reference. For example, ``heads`` or ``tags``. + + """ + return self._get( + self.make_request('git_data.references.list', user=user, repo=repo) + ) + + def create(self, body, user=None, repo=None): + """Create a reference + + :param dict body: Data describing the reference to create + :param str user: username + :param str repo: repository name + + """ + return self._post( + self.make_request('git_data.references.create', body=body, + user=user, repo=repo) + ) + + def update(self, ref, body, user=None, repo=None): + """Update an existing reference + + :param str ref: The SHA of the reference to update + :param dict body: data + + """ + return self._patch( + self.make_request('git_data.references.update', ref=ref, body=body, + user=user, repo=repo) + ) + + def delete(self, ref, user=None, repo=None): + """Delete a reference + + :param str ref: The SHA of the reference to delete + + """ + return self._delete( + self.make_request('git_data.references.delete', ref=ref, user=user, + repo=repo) + ) diff --git a/pygithub3/services/git_data/tags.py b/pygithub3/services/git_data/tags.py new file mode 100644 index 0000000..03d38ac --- /dev/null +++ b/pygithub3/services/git_data/tags.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service + + +class Tags(Service): + """Consume `Tags API `_""" + + def get(self, sha, user=None, repo=None): + """Get a tag + + :param str sha: The sha of the tag to get. + :param str user: Username + :param str repo: Repository + + """ + return self._get( + self.make_request('git_data.tags.get', sha=sha, user=user, + repo=repo) + ) + + def create(self, body, user=None, repo=None): + """Create a tag + + :param dict body: Data describing the tag to create + :param str user: Username + :param str repo: Repository + + """ + return self._post( + self.make_request('git_data.tags.create', body=body, user=user, + repo=repo) + ) diff --git a/pygithub3/services/git_data/trees.py b/pygithub3/services/git_data/trees.py new file mode 100644 index 0000000..00e010b --- /dev/null +++ b/pygithub3/services/git_data/trees.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service + + +class Trees(Service): + """Consume `Trees API `_""" + + def get(self, sha, recursive=False, user=None, repo=None): + """Get a tree object + + :param str sha: The SHA of the tree you want. + :param bool recursive: Whether to resolve each sub-tree belonging to + this tree + :param str user: Username + :param str repo: Repository + + """ + return self._get( + self.make_request('git_data.trees.get', sha=sha, + recursive=recursive, user=user, repo=repo) + ) + + def create(self, body, user=None, repo=None): + """Create a tree object + + :param dict body: Data describing the tree to create + + """ + return self._post( + self.make_request('git_data.trees.create', body=body, user=user, + repo=repo) + ) diff --git a/pygithub3/tests/services/test_git_data.py b/pygithub3/tests/services/test_git_data.py new file mode 100644 index 0000000..45ef6d1 --- /dev/null +++ b/pygithub3/tests/services/test_git_data.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import requests +from mock import patch, Mock + +from pygithub3.tests.utils.core import TestCase +from pygithub3.services.git_data import (Blobs, Commits, References, Tags, + Trees) +from pygithub3.resources.base import json +from pygithub3.tests.utils.base import (mock_response, mock_response_result, + mock_json) +from pygithub3.tests.utils.services import _ + + +json.dumps = Mock(side_effect=mock_json) +json.loads = Mock(side_effect=mock_json) + + +@patch.object(requests.sessions.Session, 'request') +class TestBlobsService(TestCase): + def setUp(self): + self.service = Blobs(user='octocat', repo='repo') + + def test_GET(self, reqm): + reqm.return_value = mock_response() + self.service.get('abc123') + self.assertEqual(reqm.call_args[0], + ('get', _('repos/octocat/repo/git/blobs/abc123'))) + + def test_CREATE(self, reqm): + reqm.return_value = mock_response('post') + self.service.create({'content': 'hello, friends', 'encoding': + 'utf-8'}) + self.assertEqual(reqm.call_args[0], + ('post', _('repos/octocat/repo/git/blobs'))) + + +@patch.object(requests.sessions.Session, 'request') +class TestCommitsService(TestCase): + def setUp(self): + self.service = Commits(user='octocat', repo='repo') + + def test_GET(self, reqm): + reqm.return_value = mock_response() + self.service.get('abc123') + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/octocat/repo/git/commits/abc123')) + ) + + def test_CREATE(self, reqm): + reqm.return_value = mock_response('post') + self.service.create({ + 'message': 'hello', + 'tree': 'abc123', + 'parents': ['mom', 'dad'], + }) + self.assertEqual( + reqm.call_args[0], + ('post', _('repos/octocat/repo/git/commits')) + ) + +@patch.object(requests.sessions.Session, 'request') +class TestReferencesService(TestCase): + def setUp(self): + self.service = References(user='user', repo='repo') + + def test_GET(self, reqm): + reqm.return_value = mock_response() + self.service.get('heads/fnord') + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/git/refs/heads/fnord')) + ) + + def test_LIST(self, reqm): + reqm.return_value = mock_response() + self.service.list() + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/git/refs')) + ) + + def test_create(self, reqm): + reqm.return_value = mock_response('post') + self.service.create({'sha': 'hello', 'ref': 'something'}) + self.assertEqual( + reqm.call_args[0], + ('post', _('repos/user/repo/git/refs')) + ) + + def test_update(self, reqm): + reqm.return_value = mock_response('patch') + self.service.update('master', {'sha': 'abc123'}) + self.assertEqual( + reqm.call_args[0], + ('patch', _('repos/user/repo/git/refs/master')) + ) + + def test_delete(self, reqm): + reqm.return_value = mock_response('delete') + self.service.delete('branch') + self.assertEqual( + reqm.call_args[0], + ('delete', _('repos/user/repo/git/refs/branch')) + ) + +@patch.object(requests.sessions.Session, 'request') +class TestTagsService(TestCase): + def setUp(self): + self.service = Tags(user='user', repo='repo') + + def test_GET(self, reqm): + reqm.return_value = mock_response() + self.service.get('abc123') + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/git/tags/abc123')) + ) + + def test_CREATE(self, reqm): + reqm.return_value = mock_response('post') + self.service.create({'tag': 'v1.2.3', 'message': 'a tag', + 'object': 'abc123', 'type': 'commit'}) + self.assertEqual( + reqm.call_args[0], + ('post', _('repos/user/repo/git/tags')) + ) + + +@patch.object(requests.sessions.Session, 'request') +class TestTreesService(TestCase): + def setUp(self): + self.service = Trees(user='user', repo='repo') + + def test_GET(self, reqm): + reqm.return_value = mock_response() + self.service.get('abc123') + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/git/trees/abc123')) + ) + + def test_GET_recursive(self, reqm): + reqm.return_value = mock_response() + self.service.get('abc123', recursive=True) + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/git/trees/abc123?recursive=1')) + ) + + def test_CREATE(self, reqm): + reqm.return_value = mock_response('post') + self.service.create({ + 'tree': [ + {'path': 'foo.txt', 'mode': '100644', 'type': 'blob', + 'sha': 'abc123'}, + ], + }) + self.assertEqual( + reqm.call_args[0], + ('post', _('repos/user/repo/git/trees')) + ) From 286cf17b23890b709a7b55ecb21148af88577779 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Mon, 16 Apr 2012 13:48:54 -0500 Subject: [PATCH 02/78] add some docs I missed --- pygithub3/requests/git_data/blobs.py | 2 -- pygithub3/requests/git_data/commits.py | 2 -- pygithub3/services/git_data/blobs.py | 10 +++++++++- pygithub3/services/git_data/commits.py | 8 +++++++- pygithub3/services/git_data/references.py | 16 +++++++++++++--- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pygithub3/requests/git_data/blobs.py b/pygithub3/requests/git_data/blobs.py index 4a49c6b..9ce500a 100644 --- a/pygithub3/requests/git_data/blobs.py +++ b/pygithub3/requests/git_data/blobs.py @@ -5,13 +5,11 @@ class Get(Request): - uri = 'repos/{user}/{repo}/git/blobs/{sha}' resource = Blob class Create(Request): - uri = 'repos/{user}/{repo}/git/blobs' resource = Blob body_schema = { diff --git a/pygithub3/requests/git_data/commits.py b/pygithub3/requests/git_data/commits.py index 936d3c7..caf1e7d 100644 --- a/pygithub3/requests/git_data/commits.py +++ b/pygithub3/requests/git_data/commits.py @@ -16,5 +16,3 @@ class Create(Request): 'schema': ('message', 'tree', 'parents', 'author', 'committer'), 'required': ('message', 'tree', 'parents'), } - - diff --git a/pygithub3/services/git_data/blobs.py b/pygithub3/services/git_data/blobs.py index d5baa2d..4f1a6e7 100644 --- a/pygithub3/services/git_data/blobs.py +++ b/pygithub3/services/git_data/blobs.py @@ -11,6 +11,8 @@ def get(self, sha, user=None, repo=None): """Get a particular blob :param str sha: The sha of the blob to get + :param str user: Username + :param str repo: Repository """ request = self.make_request('git_data.blobs.get', sha=sha, @@ -18,7 +20,13 @@ def get(self, sha, user=None, repo=None): return self._get(request) def create(self, data, user=None, repo=None): - """Create a blob""" + """Create a blob + + :param dict data: Data describing the blob to create + :param str user: Username + :param str repo: Repository + + """ request = self.make_request('git_data.blobs.create', body=data, user=user, repo=repo) return self._post(request) diff --git a/pygithub3/services/git_data/commits.py b/pygithub3/services/git_data/commits.py index cdca300..4debd95 100644 --- a/pygithub3/services/git_data/commits.py +++ b/pygithub3/services/git_data/commits.py @@ -8,7 +8,13 @@ class Commits(Service): """Consume `Commits API `_""" def get(self, sha, user=None, repo=None): - """get a commit from the current repo""" + """get a commit from the current repo + + :param str sha: SHA of the Commit that you want. + :param str user: Username + :param str repo: Repository + + """ request = self.make_request('git_data.commits.get', sha=sha, user=user, repo=repo) return self._get(request) diff --git a/pygithub3/services/git_data/references.py b/pygithub3/services/git_data/references.py index 8ae0865..3a24f90 100644 --- a/pygithub3/services/git_data/references.py +++ b/pygithub3/services/git_data/references.py @@ -13,6 +13,10 @@ def get(self, ref, user=None, repo=None): .. note:: Remember that branch references look like "heads/" + :param str ref: the name of the reference to get + :param str user: Username + :param str repo: Repository + """ return self._get( self.make_request('git_data.references.get', ref=ref, user=user, @@ -24,6 +28,8 @@ def list(self, namespace='', user=None, repo=None): :param str namespace: Limit the request to a particular type of reference. For example, ``heads`` or ``tags``. + :param str user: Username + :param str repo: Repository """ return self._get( @@ -34,8 +40,8 @@ def create(self, body, user=None, repo=None): """Create a reference :param dict body: Data describing the reference to create - :param str user: username - :param str repo: repository name + :param str user: Username + :param str repo: Repository """ return self._post( @@ -47,7 +53,9 @@ def update(self, ref, body, user=None, repo=None): """Update an existing reference :param str ref: The SHA of the reference to update - :param dict body: data + :param dict body: Data to update the reference with + :param str user: Username + :param str repo: Repository """ return self._patch( @@ -59,6 +67,8 @@ def delete(self, ref, user=None, repo=None): """Delete a reference :param str ref: The SHA of the reference to delete + :param str user: Username + :param str repo: Repository """ return self._delete( From f1e781f49d89aff903d8b755fbc6733937673828 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Mon, 16 Apr 2012 15:37:48 -0500 Subject: [PATCH 03/78] def != class --- pygithub3/services/git_data/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygithub3/services/git_data/__init__.py b/pygithub3/services/git_data/__init__.py index a12ddea..4a3ff9f 100644 --- a/pygithub3/services/git_data/__init__.py +++ b/pygithub3/services/git_data/__init__.py @@ -8,7 +8,7 @@ from .tags import Tags from .trees import Trees -def GitData(Service): +class GitData(Service): """Consume `Git Data API `_""" def __init__(self, **config): From 30c1cec25eff0a37ad494f57bfc8fce7dedf5de2 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Mon, 16 Apr 2012 15:39:27 -0500 Subject: [PATCH 04/78] add docs and a few little pep8 tweaks --- docs/git_data.rst | 86 ++++++++++++++++++++++++++ docs/services.rst | 1 + pygithub3/requests/git_data/blobs.py | 2 +- pygithub3/requests/git_data/tags.py | 1 - pygithub3/resources/gists.py | 1 + pygithub3/resources/git_data.py | 3 +- pygithub3/services/git_data/commits.py | 2 - 7 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 docs/git_data.rst diff --git a/docs/git_data.rst b/docs/git_data.rst new file mode 100644 index 0000000..549420b --- /dev/null +++ b/docs/git_data.rst @@ -0,0 +1,86 @@ +.. _Git Data service: + +Git Data services +================= + +**Example**:: + + from pygithub3 import Github + + gh = Github(user='someone', repo='some_repo') + + a_blob = gh.git_data.blobs.get('a long sha') + + dev_branch = gh.git_data.references.get('heads/add_a_thing') + + +GitData +------- + +.. autoclass:: pygithub3.services.git_data.GitData + :members: + + .. attribute:: blobs + + :ref:`Blobs service` + + .. attribute:: commits + + :ref:`Commits service` + + .. attribute:: references + + :ref:`References service` + + .. attribute:: tags + + :ref:`Tags service` + + .. attribute:: trees + + :ref:`Trees service` + + +.. _Blobs service: + +Blobs +----- + +.. autoclass:: pygithub3.services.git_data.Blobs + :members: + + +.. _Commits service: + +Commits +------- + +.. autoclass:: pygithub3.services.git_data.Commits + :members: + + +.. _References service: + +References +---------- + +.. autoclass:: pygithub3.services.git_data.References + :members: + + +.. _Tags service: + +Tags +---- + +.. autoclass:: pygithub3.services.git_data.Tags + :members: + + +.. _Trees service: + +Trees +----- + +.. autoclass:: pygithub3.services.git_data.Trees + :members: diff --git a/docs/services.rst b/docs/services.rst index 71fa690..2fbd2ee 100644 --- a/docs/services.rst +++ b/docs/services.rst @@ -72,5 +72,6 @@ List of services users repos gists + git_data .. _mimetypes: http://developer.github.com/v3/mime diff --git a/pygithub3/requests/git_data/blobs.py b/pygithub3/requests/git_data/blobs.py index 9ce500a..a4bddd6 100644 --- a/pygithub3/requests/git_data/blobs.py +++ b/pygithub3/requests/git_data/blobs.py @@ -14,5 +14,5 @@ class Create(Request): resource = Blob body_schema = { 'schema': ('content', 'encoding'), - 'required': ('content', 'encoding'), #TODO: is enc really required? + 'required': ('content', 'encoding'), # TODO: is enc really required? } diff --git a/pygithub3/requests/git_data/tags.py b/pygithub3/requests/git_data/tags.py index 8b37f0e..dbc8da4 100644 --- a/pygithub3/requests/git_data/tags.py +++ b/pygithub3/requests/git_data/tags.py @@ -13,4 +13,3 @@ class Create(Request): 'schema': ('tag', 'message', 'object', 'type', 'tagger'), 'required': ('type',), } - diff --git a/pygithub3/resources/gists.py b/pygithub3/resources/gists.py index 7e9550a..89425bf 100644 --- a/pygithub3/resources/gists.py +++ b/pygithub3/resources/gists.py @@ -15,6 +15,7 @@ class Fork(Resource): _dates = ('created_at', ) _maps = {'user': User} + def __str__(self): return '' diff --git a/pygithub3/resources/git_data.py b/pygithub3/resources/git_data.py index 4adcf5d..4d12e01 100644 --- a/pygithub3/resources/git_data.py +++ b/pygithub3/resources/git_data.py @@ -17,7 +17,8 @@ def __str__(self): class Tag(Resource): _maps = {'object': Commit, - 'tagger': Author,} # committer? tagger? + 'tagger': Author} # committer? tagger? + def __str__(self): return '' % getattr(self, 'tag', '') diff --git a/pygithub3/services/git_data/commits.py b/pygithub3/services/git_data/commits.py index 4debd95..25e8775 100644 --- a/pygithub3/services/git_data/commits.py +++ b/pygithub3/services/git_data/commits.py @@ -31,5 +31,3 @@ def create(self, data, user=None, repo=None): self.make_request('git_data.commits.create', user=user, repo=repo, body=data) ) - - From 9517cf574615ba9b18fe9f0807b1f1f6cc540357 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Mon, 16 Apr 2012 16:05:51 -0500 Subject: [PATCH 05/78] add test_requirements.txt and more test running instructions --- README.rst | 8 ++++++-- docs/installation.rst | 14 ++++++++++---- test_requirements.txt | 4 ++++ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 test_requirements.txt diff --git a/README.rst b/README.rst index d37650b..cc787d8 100644 --- a/README.rst +++ b/README.rst @@ -58,5 +58,9 @@ Contribute 4. Add you to ``AUTHORS`` 5. Pull request it -**Note**: I use `nose `_ test environment, -with `mock `_ ``pip install nose mock`` +Tests +----- + +Test and docs requirements are listed in ``test_requirements.txt``. +Run ``pip install -r test_requirements.txt`` to install them and ``nosetests`` +to run tests diff --git a/docs/installation.rst b/docs/installation.rst index 8ed6c10..6eccc95 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -15,15 +15,21 @@ Dependencies Required ......... -This library only depends on `request library`_ module. +This library depends only on the `requests`_ module. -If you install ``pygithub3`` with ``pip`` all is done. The best option +If you install ``pygithub3`` with ``pip`` all is done. This is the best option. Optional ......... -If you want to run the test suite, you must install `nose`_ and `mock`_ +The test suite uses `nose`_, `mock`_, and `unittest2`_. Compiling the +documentation requires `sphinx`_. -.. _request library: http://docs.python-requests.org/en/v0.10.6/index.html +Install all of these by running ``pip install -r test_requirements.txt``. Then +just run ``nosetests`` to run the tests. + +.. _requests: http://docs.python-requests.org/en/v0.10.6/index.html .. _nose: http://readthedocs.org/docs/nose/en/latest .. _mock: http://pypi.python.org/pypi/mock +.. _unittest2: http://pypi.python.org/pypi/unittest2 +.. _sphinx: http://sphinx.pocoo.org/ diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 0000000..862d240 --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1,4 @@ +nose +unittest2 +mock +sphinx From ffa3643b14bfc14b8c72596c13012b3cf2aa444f Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Mon, 16 Apr 2012 16:07:10 -0500 Subject: [PATCH 06/78] add myself to AUTHORS and some other docs tweaks --- AUTHORS.rst | 6 ++++-- README.rst | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index af05118..4415d5e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -11,8 +11,9 @@ Forked and redesign from Kenneth Reitz's repo Forked from https://github.com/kennethreitz/python-github3 -Kenneth Reitz is also the author of the awesome `requests `_ library -which `python-github3` needs it +Kenneth Reitz is also the author of the awesome +`requests `_ library, which +`python-github3` requires. Patches and Suggestions ......................... @@ -21,3 +22,4 @@ Patches and Suggestions - Rok Garbas - Antti Kaihola - Francisco Marcos +- Nathaniel Williams diff --git a/README.rst b/README.rst index cc787d8..1591e38 100644 --- a/README.rst +++ b/README.rst @@ -41,11 +41,12 @@ Achievements - `Users service `_ - `Repos service `_ - `Gists service `_ +- `Git Data service `_ TODO ----- -- Services: Git Data, Issues, Orgs, Pull Requests, Events +- Services: Issues, Orgs, Pull Requests, Events - Oauth authorization API (service?) - Proxy methods into resources (e.g copitux.followers()) @@ -55,7 +56,7 @@ Contribute 1. Fork the `repository `_ 2. Write a test to cover new feature or to reproduce bug 3. Code with `pep8 `_ rules -4. Add you to ``AUTHORS`` +4. Add yourself to ``AUTHORS`` 5. Pull request it Tests @@ -63,4 +64,4 @@ Tests Test and docs requirements are listed in ``test_requirements.txt``. Run ``pip install -r test_requirements.txt`` to install them and ``nosetests`` -to run tests +to run tests. From 2ca15bb8e847735f566ee0cb896f071b3b5ca056 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Tue, 17 Apr 2012 14:39:12 -0500 Subject: [PATCH 07/78] let request objects specify custom body validations --- pygithub3/requests/base.py | 14 ++++++++++---- pygithub3/tests/requests/test_core.py | 17 +++++++++++++++-- pygithub3/tests/utils/base.py | 13 ++++++++++++- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/pygithub3/requests/base.py b/pygithub3/requests/base.py index 03b0f8a..582a73f 100644 --- a/pygithub3/requests/base.py +++ b/pygithub3/requests/base.py @@ -17,10 +17,11 @@ class Body(object): - def __init__(self, content, schema, required): + def __init__(self, content, valid_body, validate_body=None): self.content = content - self.schema = schema - self.required = required + self.schema = valid_body['schema'] + self.required = valid_body['required'] + self.validate_body = validate_body or (lambda x: None) def dumps(self): if not self.schema: @@ -33,6 +34,7 @@ def parse(self): % self.__class__.__name__) parsed = dict([(key, self.content[key]) for key in self.schema if key in self.content]) + self.validate_body(parsed) for attr_required in self.required: if attr_required not in parsed: raise ValidationError("'%s' attribute is required" % @@ -58,7 +60,8 @@ def __init__(self, **kwargs): def clean(self): self.uri = self.clean_uri() or self.uri - self.body = Body(self.clean_body(), **self.clean_valid_body()) + self.body = Body(self.clean_body(), self.clean_valid_body(), + self.validate_body) def clean_body(self): return self.body @@ -94,6 +97,9 @@ def populate_uri(self): def get_body(self): return self.body.dumps() + def validate_body(self, *args): + pass + class Factory(object): """ Request builder """ diff --git a/pygithub3/tests/requests/test_core.py b/pygithub3/tests/requests/test_core.py index cd162b3..9fcec10 100644 --- a/pygithub3/tests/requests/test_core.py +++ b/pygithub3/tests/requests/test_core.py @@ -2,12 +2,14 @@ # -*- encoding: utf-8 -*- from mock import Mock +from nose.tools import raises from pygithub3.tests.utils.core import TestCase from pygithub3.requests.base import Factory, Body, json, Request from pygithub3.exceptions import (UriInvalid, DoesNotExists, ValidationError, InvalidBodySchema) -from pygithub3.tests.utils.base import mock_json, DummyRequest +from pygithub3.tests.utils.base import (mock_json, DummyRequest, + DummyRequestValidation) from pygithub3.tests.utils.requests import ( RequestWithArgs, RequestCleanedUri, RequestBodyInvalidSchema, RequestCleanedBody) @@ -74,7 +76,7 @@ class TestRequestBodyWithSchema(TestCase): def setUp(self): valid_body = dict(schema=('arg1', 'arg2'), required=('arg1', )) - self.b = Body({}, **valid_body) + self.b = Body({}, valid_body) def test_with_body_empty_and_schema_permissive(self): self.b.schema = ('arg1', 'arg2', '...') @@ -100,3 +102,14 @@ def test_with_body_as_None(self): def test_only_valid_keys(self): self.b.content = dict(arg1='arg1', arg2='arg2', fake='test') self.assertEqual(self.b.dumps(), dict(arg1='arg1', arg2='arg2')) + + +class TestBodyValidation(TestCase): + @raises(ValidationError) + def test_with_error(self): + req = DummyRequestValidation( + body={'foo': 'bar', 'error': 'yes'}, + ) + req.body_schema = {'schema': ('foo',), + 'required': ('foo',)} + req.get_body() diff --git a/pygithub3/tests/utils/base.py b/pygithub3/tests/utils/base.py index 49ea2b4..fb519e9 100644 --- a/pygithub3/tests/utils/base.py +++ b/pygithub3/tests/utils/base.py @@ -4,7 +4,7 @@ from mock import Mock from pygithub3.resources.base import Resource -from pygithub3.requests.base import Request +from pygithub3.requests.base import Request, ValidationError def mock_json(content): @@ -37,3 +37,14 @@ def loads_mock(content): class DummyRequest(Request): uri = 'dummyrequest' resource = DummyResource + + +class DummyRequestValidation(DummyRequest): + body_schema = { + 'schema': ('foo', 'error'), + 'required': ('foo',) + } + + def validate_body(self, body): + if body.get('error') == 'yes': + raise ValidationError('yes') From 17649c939901573af53beeeeb9d7be08102d1503 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Tue, 17 Apr 2012 14:42:57 -0500 Subject: [PATCH 08/78] more specific exception for missing Request classes --- pygithub3/exceptions.py | 2 +- pygithub3/requests/base.py | 12 ++++++------ pygithub3/tests/requests/test_core.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pygithub3/exceptions.py b/pygithub3/exceptions.py index a467256..20cf058 100644 --- a/pygithub3/exceptions.py +++ b/pygithub3/exceptions.py @@ -8,7 +8,7 @@ class InvalidBodySchema(Exception): pass -class DoesNotExists(Exception): +class RequestDoesNotExist(Exception): """ Raised when `Request` factory can't find the subclass """ pass diff --git a/pygithub3/requests/base.py b/pygithub3/requests/base.py index 582a73f..d3e18a3 100644 --- a/pygithub3/requests/base.py +++ b/pygithub3/requests/base.py @@ -7,8 +7,8 @@ except ImportError: import json -from pygithub3.exceptions import (DoesNotExists, UriInvalid, ValidationError, - InvalidBodySchema) +from pygithub3.exceptions import (RequestDoesNotExist, UriInvalid, + ValidationError, InvalidBodySchema) from pygithub3.resources.base import Raw from pygithub3.core.utils import import_module @@ -126,11 +126,11 @@ def wrapper(self, request_uri, **kwargs): % (ABS_IMPORT_PREFIX, module_chunk)) request = getattr(module, request_chunk) except ImportError: - raise DoesNotExists("'%s' module does not exists" - % module_chunk) + raise RequestDoesNotExist("'%s' module does not exist" + % module_chunk) except AttributeError: - raise DoesNotExists( - "'%s' request doesn't exists into '%s' module" + raise RequestDoesNotExist( + "'%s' request does not exist in '%s' module" % (request_chunk, module_chunk)) return func(self, request, **kwargs) return wrapper diff --git a/pygithub3/tests/requests/test_core.py b/pygithub3/tests/requests/test_core.py index 9fcec10..4632056 100644 --- a/pygithub3/tests/requests/test_core.py +++ b/pygithub3/tests/requests/test_core.py @@ -6,8 +6,8 @@ from pygithub3.tests.utils.core import TestCase from pygithub3.requests.base import Factory, Body, json, Request -from pygithub3.exceptions import (UriInvalid, DoesNotExists, ValidationError, - InvalidBodySchema) +from pygithub3.exceptions import (UriInvalid, RequestDoesNotExist, + ValidationError, InvalidBodySchema) from pygithub3.tests.utils.base import (mock_json, DummyRequest, DummyRequestValidation) from pygithub3.tests.utils.requests import ( @@ -29,8 +29,8 @@ def test_BUILDER_with_invalid_action(self): self.assertRaises(UriInvalid, self.f, '.invalid') def test_BUILDER_with_fake_action(self): - self.assertRaises(DoesNotExists, self.f, 'users.fake') - self.assertRaises(DoesNotExists, self.f, 'fake.users') + self.assertRaises(RequestDoesNotExist, self.f, 'users.fake') + self.assertRaises(RequestDoesNotExist, self.f, 'fake.users') def test_BUILDER_builds_users(self): """ Users.get as real test because it wouldn't be useful mock From 4539f80aa44e500422c5071d8e3d74de321b6225 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Tue, 17 Apr 2012 17:35:31 -0500 Subject: [PATCH 09/78] add pull request API there are a few little issues remaining. Mostly regarding handling meaningful non-20x response codes --- pygithub3/core/client.py | 2 +- pygithub3/exceptions.py | 2 +- pygithub3/github.py | 9 + pygithub3/requests/pull_requests/__init__.py | 62 ++++++ pygithub3/requests/pull_requests/comments.py | 43 ++++ pygithub3/resources/pull_requests.py | 20 ++ pygithub3/services/pull_requests/__init__.py | 129 +++++++++++ pygithub3/services/pull_requests/comments.py | 73 +++++++ .../tests/services/test_pull_requests.py | 201 ++++++++++++++++++ pygithub3/tests/utils/base.py | 2 +- 10 files changed, 540 insertions(+), 3 deletions(-) create mode 100644 pygithub3/requests/pull_requests/__init__.py create mode 100644 pygithub3/requests/pull_requests/comments.py create mode 100644 pygithub3/resources/pull_requests.py create mode 100644 pygithub3/services/pull_requests/__init__.py create mode 100644 pygithub3/services/pull_requests/comments.py create mode 100644 pygithub3/tests/services/test_pull_requests.py diff --git a/pygithub3/core/client.py b/pygithub3/core/client.py index ee7c97f..eadb18e 100644 --- a/pygithub3/core/client.py +++ b/pygithub3/core/client.py @@ -81,7 +81,7 @@ def request(self, verb, request, **kwargs): def get(self, request, **kwargs): response = self.request('get', request, **kwargs) - assert response.status_code == 200 + # there are valid GET responses that != 200 return response def post(self, request, **kwargs): diff --git a/pygithub3/exceptions.py b/pygithub3/exceptions.py index 20cf058..9ee2597 100644 --- a/pygithub3/exceptions.py +++ b/pygithub3/exceptions.py @@ -37,6 +37,6 @@ class UnprocessableEntity(Exception): class NotFound(Exception): """ Raised when server response is 404 - Catched with a pygithub3-exception to `services.base.Service._bool` method + Caught with a pygithub3-exception to `services.base.Service._bool` method """ pass diff --git a/pygithub3/github.py b/pygithub3/github.py index 0b302a1..0f15adf 100644 --- a/pygithub3/github.py +++ b/pygithub3/github.py @@ -17,9 +17,11 @@ def __init__(self, **config): from pygithub3.services.users import User from pygithub3.services.repos import Repo from pygithub3.services.gists import Gist + from pygithub3.services.pull_requests import PullRequests self._users = User(**config) self._repos = Repo(**config) self._gists = Gist(**config) + self._pull_requests = PullRequests(**config) @property def remaining_requests(self): @@ -47,3 +49,10 @@ def gists(self): :ref:`Gists service ` """ return self._gists + + @property + def pull_requests(self): + """ + :ref:`Pull Requests service ' % getattr(self, 'title', '') + + +class File(Resource): + def __str__(self): + return '' % getattr(self, 'filename', '') + + +class Comment(Resource): + _dates = ('created_at', 'updated_at') + + def __str__(self): + return '' % getattr(self, 'id', '') diff --git a/pygithub3/services/pull_requests/__init__.py b/pygithub3/services/pull_requests/__init__.py new file mode 100644 index 0000000..2197f60 --- /dev/null +++ b/pygithub3/services/pull_requests/__init__.py @@ -0,0 +1,129 @@ +from pygithub3.exceptions import BadRequest, NotFound +from pygithub3.services.base import Service, MimeTypeMixin +from .comments import Comments + + +class PullRequests(Service, MimeTypeMixin): + """Consume `Pull Request API `_""" + + def __init__(self, **config): + self.comments = Comments(**config) + super(PullRequests, self).__init__(**config) + + def list(self, user=None, repo=None): + """List all of the pull requests for a repo + + :param str user: Username + :param str repo: Repository + + """ + return self._get_result( + self.make_request('pull_requests.list', user=user, repo=repo) + ) + + def get(self, number, user=None, repo=None): + """Get a single pull request + + :param str number: The number of the pull request to get + :param str user: Username + :param str repo: Repository + + """ + return self._get( + self.make_request('pull_requests.get', number=number, user=user, + repo=repo) + ) + + def create(self, body, user=None, repo=None): + """Create a pull request + + :param dict body: Data for the new pull request + :param str user: Username + :param str repo: Repository + + """ + return self._post( + self.make_request('pull_requests.create', body=body, user=user, + repo=repo) + ) + + def update(self, number, body, user=None, repo=None): + """Update a pull request + + :param str number: The number of the the pull request to update + :param dict body: The data to update the pull request with + :param str user: Username + :param str repo: Repository + + """ + return self._patch( + self.make_request('pull_requests.update', number=number, + body=body, user=user, repo=repo) + ) + + def list_commits(self, number, user=None, repo=None): + """List the commits for a pull request + + :param str number: The number of the pull request to list commits for + :param str user: Username + :param str repo: Repository + + """ + return self._get_result( + self.make_request('pull_requests.list_commits', number=number, + user=user, repo=repo) + ) + + def list_files(self, number, user=None, repo=None): + """List the files for a pull request + + :param str number: The number of the pull request to list files for + :param str user: Username + :param str repo: Repository + + """ + return self._get_result( + self.make_request('pull_requests.list_files', number=number, + user=user, repo=repo) + ) + + def merge_status(self, number, user=None, repo=None): + """Gets whether a pull request has been merged or not. + + :param str number: The pull request to check + :param str user: Username + :param str repo: Repository + + """ + # for this to work with a proper Resource, we would need to pass the + # response's status code to the Resource constructor, and that's kind + # of scary + try: + resp = self._client.get( + self.make_request('pull_requests.merge_status', number=number, + user=user, repo=repo) + ) + except NotFound: + return False + code = resp.status_code + if code == 204: + return True + # TODO: more flexible way to return arbitrary objects based on + # response. Probably something on Request + raise BadRequest('got code %s: %s' % (code, resp.content)) + # again, I'm sorry. + + def merge(self, number, message='', user=None, repo=None): + """Merge a pull request. + + :param str number: The pull request to merge + :param str user: Username + :param str repo: Repository + + """ + # so, the API docs don't actually say what the status code will be in + # the case of a merge failure. I hope it's not a 404. + return self._put( + self.make_request('pull_requests.merge', number=number, + message=message, user=user, repo=repo) + ) diff --git a/pygithub3/services/pull_requests/comments.py b/pygithub3/services/pull_requests/comments.py new file mode 100644 index 0000000..3aa6d0e --- /dev/null +++ b/pygithub3/services/pull_requests/comments.py @@ -0,0 +1,73 @@ +from pygithub3.services.base import Service, MimeTypeMixin + + +class Comments(Service, MimeTypeMixin): + """Consume `Review Comments API + `_ + + """ + + def list(self, number, user=None, repo=None): + """List all the comments for a pull request + + :param str number: The number of the pull request + :param str user: Username + :param str repo: Repository + + """ + return self._get_result( + self.make_request('pull_requests.comments.list', number=number, + user=user, repo=repo) + ) + + def get(self, number, user=None, repo=None): + """Get a single comment + + :param str number: The comment to get + :param str user: Username + :param str repo: Repository + + """ + return self._get( + self.make_request('pull_requests.comments.get', number=number, + user=user, repo=repo) + ) + + def create(self, number, body, user=None, repo=None): + """Create a comment + + :param str number: the pull request to comment on + :param str user: Username + :param str repo: Repository + + """ + return self._post( + self.make_request('pull_requests.comments.create', number=number, + body=body, user=user, repo=repo) + ) + + def edit(self, number, body, user=None, repo=None): + """Edit a comment + + :param str number: The id of the comment to edit + :param str user: Username + :param str repo: Repository + + """ + return self._patch( + self.make_request('pull_requests.comments.edit', number=number, + body=body, user=user, repo=repo) + ) + + def delete(self, number, user=None, repo=None): + """Delete a comment + + :param str number: The comment to delete + :param str user: Username + :param str repo: Repository + + """ + return self._delete( + self.make_request('pull_requests.comments.delete', number=number, + user=user, repo=repo) + ) diff --git a/pygithub3/tests/services/test_pull_requests.py b/pygithub3/tests/services/test_pull_requests.py new file mode 100644 index 0000000..8071b09 --- /dev/null +++ b/pygithub3/tests/services/test_pull_requests.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import requests +from mock import patch, Mock +from nose.tools import raises + +from pygithub3.tests.utils.core import TestCase +from pygithub3.services.pull_requests import PullRequests, Comments +from pygithub3.resources.base import json +from pygithub3.requests.base import ValidationError +from pygithub3.tests.utils.base import (mock_response, mock_response_result, + mock_json) +from pygithub3.tests.utils.services import _ + + +json.dumps = Mock(side_effect=mock_json) +json.loads = Mock(side_effect=mock_json) + + +@patch.object(requests.sessions.Session, 'request') +class TestPullRequestsService(TestCase): + def setUp(self): + self.service = PullRequests(user='user', repo='repo') + + def test_LIST(self, reqm): + reqm.return_value = mock_response_result() + self.service.list().all() + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/pulls')) + ) + + def test_GET(self, reqm): + reqm.return_value = mock_response() + self.service.get(123) + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/pulls/123')) + ) + + def test_CREATE_with_title_and_body(self, reqm): + reqm.return_value = mock_response('post') + data = { + 'title': 'this is a pull request', + 'body': 'merge me!', + 'head': 'octocat:some-feature', + 'base': 'master', + } + self.service.create(data) + self.assertEqual( + reqm.call_args[0], + ('post', _('repos/user/repo/pulls')) + ) + + def test_CREATE_with_issue(self, reqm): + reqm.return_value = mock_response('post') + data = { + 'issue': 1, + 'head': 'octocat:some-feature', + 'base': 'master', + } + self.service.create(data) + self.assertEqual( + reqm.call_args[0], + ('post', _('repos/user/repo/pulls')) + ) + + @raises(ValidationError) + def test_CREATE_with_no_title(self, reqm): + reqm.return_value = mock_response('post') + data = { + 'body': 'merge me!', + 'head': 'octocat:some-feature', + 'base': 'master', + } + self.service.create(data) + + def test_UPDATE(self, reqm): + reqm.return_value = mock_response('patch') + data = {} + self.service.update(123, data) + self.assertEqual( + reqm.call_args[0], + ('patch', _('repos/user/repo/pulls/123')) + ) + + @raises(ValidationError) + def test_UPDATE_with_invalid_state(self, reqm): + reqm.return_value = mock_response('patch') + data = {'state': 'Illinois'} + self.service.update(123, data) + + def test_LIST_COMMITS(self, reqm): + reqm.return_value = mock_response_result('get') + self.service.list_commits(123).all() + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/pulls/123/commits')) + ) + + def test_LIST_FILES(self, reqm): + reqm.return_value = mock_response_result('get') + self.service.list_files(123).all() + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/pulls/123/files')) + ) + + def test_MERGE_STATUS_true(self, reqm): + reqm.return_value = mock_response(204) + resp = self.service.merge_status(123) + self.assertEqual(True, resp) + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/pulls/123/merge')) + ) + + def test_MERGE_STATUS_false(self, reqm): + reqm.return_value = mock_response(404) + resp = self.service.merge_status(123) + self.assertEqual(False, resp) + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/pulls/123/merge')) + ) + + def test_MERGE(self, reqm): + reqm.return_value = mock_response(200) + self.service.merge(123, 'merging this') + self.assertEqual( + reqm.call_args[0], + ('put', _('repos/user/repo/pulls/123/merge')) + ) + + +@patch.object(requests.sessions.Session, 'request') +class TestPullRequestCommentsService(TestCase): + def setUp(self): + self.service = Comments(user='user', repo='repo') + + def test_LIST(self, reqm): + reqm.return_value = mock_response_result(200) + self.service.list(123).all() + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/pulls/123/comments')) + ) + + def test_GET(self, reqm): + reqm.return_value = mock_response(200) + self.service.get(1) + self.assertEqual( + reqm.call_args[0], + ('get', _('repos/user/repo/pulls/comments/1')) + ) + + def test_CREATE(self, reqm): + reqm.return_value = mock_response(201) + data = { + 'body': ':sparkles:', + 'commit_id': 'abc123', + 'path': 'foo.txt', + 'position': '2', + } + self.service.create(1, data) + self.assertEqual( + reqm.call_args[0], + ('post', _('repos/user/repo/pulls/1/comments')) + ) + + def test_CREATE_in_reply_to(self, reqm): + reqm.return_value = mock_response(201) + data = { + 'body': ':sparkles:', + 'in_reply_to': '5', + } + self.service.create(1, data) + self.assertEqual( + reqm.call_args[0], + ('post', _('repos/user/repo/pulls/1/comments')) + ) + + def test_EDIT(self, reqm): + reqm.return_value = mock_response(200) + data = { + 'body': 'something completely different', + } + self.service.edit(1, data) + self.assertEqual( + reqm.call_args[0], + ('patch', _('repos/user/repo/pulls/comments/1')) + ) + + def test_DELETE(self, reqm): + reqm.return_value = mock_response(204) + self.service.delete(1) + self.assertEqual( + reqm.call_args[0], + ('delete', _('repos/user/repo/pulls/comments/1')) + ) diff --git a/pygithub3/tests/utils/base.py b/pygithub3/tests/utils/base.py index fb519e9..b3c3b76 100644 --- a/pygithub3/tests/utils/base.py +++ b/pygithub3/tests/utils/base.py @@ -14,7 +14,7 @@ def mock_json(content): def mock_response(status_code='get', content={}): CODES = dict(get=200, patch=200, post=201, delete=204) response = Mock(name='response') - response.status_code = CODES[str(status_code).lower()] or status_code + response.status_code = CODES.get(str(status_code).lower(), status_code) response.content = content return response From d1f86894f91b21c7c92a9154caf3815f930aa8a6 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Wed, 18 Apr 2012 14:14:55 -0500 Subject: [PATCH 10/78] pass body to pull_requests.merge properly --- pygithub3/services/pull_requests/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pygithub3/services/pull_requests/__init__.py b/pygithub3/services/pull_requests/__init__.py index 2197f60..f7da50a 100644 --- a/pygithub3/services/pull_requests/__init__.py +++ b/pygithub3/services/pull_requests/__init__.py @@ -120,10 +120,12 @@ def merge(self, number, message='', user=None, repo=None): :param str user: Username :param str repo: Repository + This currently raises an HTTP 405 error if the request is not + mergable. + """ - # so, the API docs don't actually say what the status code will be in - # the case of a merge failure. I hope it's not a 404. + body = {'commit_message': message} return self._put( self.make_request('pull_requests.merge', number=number, - message=message, user=user, repo=repo) + body=body, user=user, repo=repo) ) From b97ab378e623826ca4192ea9977cf1c91f39031b Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Wed, 18 Apr 2012 14:50:00 -0500 Subject: [PATCH 11/78] add docs for pull requests --- docs/pull_requests.rst | 33 +++++++++++++++++++++++++++++++++ docs/services.rst | 1 + 2 files changed, 34 insertions(+) create mode 100644 docs/pull_requests.rst diff --git a/docs/pull_requests.rst b/docs/pull_requests.rst new file mode 100644 index 0000000..40c3435 --- /dev/null +++ b/docs/pull_requests.rst @@ -0,0 +1,33 @@ +.. _Pull Requests service: + +Pull Requests service +===================== + +**Example**:: + + from pygithub3 import Github + + gh = Github() + + pull_requests = gh.pull_requests.list().all() + for pr in pull_requests: + commits = gh.pull_requests.list_commits(pr.number).all() + +Pull Requests +------------- + +.. autoclass:: pygithub3.services.pull_requests.PullRequests + :members: + + .. attribute:: comments + + :ref:`Pull Request Comments service` + + +.. _Pull Request Comments service: + +Pull Request Comments +--------------------- + +.. autoclass:: pygithub3.services.pull_requests.Comments + :members: diff --git a/docs/services.rst b/docs/services.rst index 71fa690..945501b 100644 --- a/docs/services.rst +++ b/docs/services.rst @@ -72,5 +72,6 @@ List of services users repos gists + pull_requests .. _mimetypes: http://developer.github.com/v3/mime From efee843e8e46894ed236811c29bb9c07e9907000 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Thu, 19 Apr 2012 09:56:56 -0500 Subject: [PATCH 12/78] merge request won't json encode body without schema --- pygithub3/requests/pull_requests/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pygithub3/requests/pull_requests/__init__.py b/pygithub3/requests/pull_requests/__init__.py index 7e85f8f..bd03f3e 100644 --- a/pygithub3/requests/pull_requests/__init__.py +++ b/pygithub3/requests/pull_requests/__init__.py @@ -60,3 +60,7 @@ class Merge_status(Request): class Merge(Request): uri = 'repos/{user}/{repo}/pulls/{number}/merge' resource = Raw + body_schema = { + 'schema': ('commit_message',), + 'required': (), + } From 9008296715194c150e7eeb16469005d771f7d370 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Thu, 19 Apr 2012 09:57:34 -0500 Subject: [PATCH 13/78] use _bool for pull request merge status --- pygithub3/services/pull_requests/__init__.py | 21 ++++--------------- .../tests/services/test_pull_requests.py | 4 ++-- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/pygithub3/services/pull_requests/__init__.py b/pygithub3/services/pull_requests/__init__.py index f7da50a..66d9e58 100644 --- a/pygithub3/services/pull_requests/__init__.py +++ b/pygithub3/services/pull_requests/__init__.py @@ -95,23 +95,10 @@ def merge_status(self, number, user=None, repo=None): :param str repo: Repository """ - # for this to work with a proper Resource, we would need to pass the - # response's status code to the Resource constructor, and that's kind - # of scary - try: - resp = self._client.get( - self.make_request('pull_requests.merge_status', number=number, - user=user, repo=repo) - ) - except NotFound: - return False - code = resp.status_code - if code == 204: - return True - # TODO: more flexible way to return arbitrary objects based on - # response. Probably something on Request - raise BadRequest('got code %s: %s' % (code, resp.content)) - # again, I'm sorry. + return self._bool( + self.make_request('pull_requests.merge_status', number=number, + user=user, repo=repo) + ) def merge(self, number, message='', user=None, repo=None): """Merge a pull request. diff --git a/pygithub3/tests/services/test_pull_requests.py b/pygithub3/tests/services/test_pull_requests.py index 8071b09..7fc15b7 100644 --- a/pygithub3/tests/services/test_pull_requests.py +++ b/pygithub3/tests/services/test_pull_requests.py @@ -113,7 +113,7 @@ def test_MERGE_STATUS_true(self, reqm): self.assertEqual(True, resp) self.assertEqual( reqm.call_args[0], - ('get', _('repos/user/repo/pulls/123/merge')) + ('head', _('repos/user/repo/pulls/123/merge')) ) def test_MERGE_STATUS_false(self, reqm): @@ -122,7 +122,7 @@ def test_MERGE_STATUS_false(self, reqm): self.assertEqual(False, resp) self.assertEqual( reqm.call_args[0], - ('get', _('repos/user/repo/pulls/123/merge')) + ('head', _('repos/user/repo/pulls/123/merge')) ) def test_MERGE(self, reqm): From 01a46be2799bed51bdd19e752fbea5f4d9687919 Mon Sep 17 00:00:00 2001 From: Nat Williams Date: Thu, 19 Apr 2012 10:54:51 -0500 Subject: [PATCH 14/78] docstring link typo --- pygithub3/github.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygithub3/github.py b/pygithub3/github.py index 0f15adf..9016742 100644 --- a/pygithub3/github.py +++ b/pygithub3/github.py @@ -53,6 +53,6 @@ def gists(self): @property def pull_requests(self): """ - :ref:`Pull Requests service ` """ return self._pull_requests From c7c03100fd0584b759bb75d461a12f5bcd5aabba Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Mon, 23 Apr 2012 17:44:10 +0200 Subject: [PATCH 15/78] Baseline Orgs API implementation --- pygithub3/github.py | 9 +++++ pygithub3/requests/orgs/__init__.py | 27 ++++++++++++++ pygithub3/resources/orgs.py | 2 +- pygithub3/services/orgs/__init__.py | 51 +++++++++++++++++++++++++++ pygithub3/tests/services/test_orgs.py | 44 +++++++++++++++++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 pygithub3/requests/orgs/__init__.py create mode 100644 pygithub3/services/orgs/__init__.py create mode 100644 pygithub3/tests/services/test_orgs.py diff --git a/pygithub3/github.py b/pygithub3/github.py index 0b302a1..ba403e6 100644 --- a/pygithub3/github.py +++ b/pygithub3/github.py @@ -17,9 +17,11 @@ def __init__(self, **config): from pygithub3.services.users import User from pygithub3.services.repos import Repo from pygithub3.services.gists import Gist + from pygithub3.services.orgs import Org self._users = User(**config) self._repos = Repo(**config) self._gists = Gist(**config) + self._orgs = Org(**config) @property def remaining_requests(self): @@ -47,3 +49,10 @@ def gists(self): :ref:`Gists service ` """ return self._gists + + @property + def orgs(self): + """ + :ref:`Orgs service ` + """ + return self._orgs diff --git a/pygithub3/requests/orgs/__init__.py b/pygithub3/requests/orgs/__init__.py new file mode 100644 index 0000000..a490a17 --- /dev/null +++ b/pygithub3/requests/orgs/__init__.py @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.orgs import Org + + +class List(Request): + uri = 'users/{user}/orgs' + resource = Org + + def clean_uri(self): + if not self.user: + return 'user/orgs' + + +class Get(Request): + uri = 'orgs/{name}' + resource = Org + + +class Update(Request): + uri = 'orgs/{name}' + resource = Org + body_schema = { + 'schema': ('billing_email', 'company', 'email', 'location', 'name'), + 'required': (), + } diff --git a/pygithub3/resources/orgs.py b/pygithub3/resources/orgs.py index 3996172..be6769a 100644 --- a/pygithub3/resources/orgs.py +++ b/pygithub3/resources/orgs.py @@ -11,4 +11,4 @@ class Org(Resource): _dates = ('created_at', ) def __str__(self): - return '' % getattr(self, 'name', '') + return '' % getattr(self, 'login', '') diff --git a/pygithub3/services/orgs/__init__.py b/pygithub3/services/orgs/__init__.py new file mode 100644 index 0000000..28f6b16 --- /dev/null +++ b/pygithub3/services/orgs/__init__.py @@ -0,0 +1,51 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service + + +class Org(Service): + """ Consume `Orgs API `_ """ + + def list(self, user=None): + """ Get user's orgs + + :param str user: Username + :returns: A :doc:`result` + + If you call it without user and you are authenticated, get the + authenticated user's orgs, public and private. + + If you call it with a user, get the user's public orgs. + + :: + + org_service.list('copitux') + org_service.list() + """ + request = self.request_builder('orgs.list', user=user) + return self._get_result(request) + + def get(self, name): + """ Get a single org + + :param str name: Org name + """ + request = self.request_builder('orgs.get', name=name) + return self._get(request) + + def update(self, name, data): + """ Update a single org + + :param str name: Org name + :param dict data: Input. See `github orgs doc`_ + + .. warning :: + You must be authenticated + + :: + + org_service.update(dict(company='ACME Development', + location='Timbuctoo')) + """ + request = self.request_builder('orgs.update', name=name, body=data) + return self._patch(request) diff --git a/pygithub3/tests/services/test_orgs.py b/pygithub3/tests/services/test_orgs.py new file mode 100644 index 0000000..a6416ff --- /dev/null +++ b/pygithub3/tests/services/test_orgs.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import requests +from mock import patch, Mock + +from pygithub3.tests.utils.core import TestCase +from pygithub3.resources.base import json +from pygithub3.services.orgs import Org +from pygithub3.tests.utils.base import (mock_response, mock_response_result, + mock_json) +from pygithub3.tests.utils.services import _ + +json.dumps = Mock(side_effect=mock_json) +json.loads = Mock(side_effect=mock_json) + + +@patch.object(requests.sessions.Session, 'request') +class TestOrgService(TestCase): + + def setUp(self): + self.org = Org() + + def test_LIST_without_user(self, request_method): + request_method.return_value = mock_response_result() + self.org.list().all() + self.assertEqual(request_method.call_args[0], ('get', _('user/orgs'))) + + def test_LIST_with_user(self, request_method): + request_method.return_value = mock_response_result() + self.org.list('octocat').all() + self.assertEqual(request_method.call_args[0], + ('get', _('users/octocat/orgs'))) + + def test_GET(self, request_method): + request_method.return_value = mock_response() + self.org.get('acme') + self.assertEqual(request_method.call_args[0], ('get', _('orgs/acme'))) + + def test_UPDATE(self, request_method): + request_method.return_value = mock_response('patch') + self.org.update('acme', {'company': 'ACME Widgets'}) + self.assertEqual(request_method.call_args[0], + ('patch', _('orgs/acme'))) From 0559014ee5957331d05e857c6a929279837b2e8e Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 24 Apr 2012 01:04:53 +0200 Subject: [PATCH 16/78] Move Team to orgs --- pygithub3/requests/repos/__init__.py | 3 ++- pygithub3/resources/orgs.py | 6 ++++++ pygithub3/resources/repos.py | 7 +------ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pygithub3/requests/repos/__init__.py b/pygithub3/requests/repos/__init__.py index cd920fe..7bbcf3e 100644 --- a/pygithub3/requests/repos/__init__.py +++ b/pygithub3/requests/repos/__init__.py @@ -1,8 +1,9 @@ # -*- encoding: utf-8 -*- from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.orgs import Team +from pygithub3.resources.repos import Repo, Tag, Branch from pygithub3.resources.users import User -from pygithub3.resources.repos import Repo, Team, Tag, Branch class List(Request): diff --git a/pygithub3/resources/orgs.py b/pygithub3/resources/orgs.py index be6769a..a79395d 100644 --- a/pygithub3/resources/orgs.py +++ b/pygithub3/resources/orgs.py @@ -12,3 +12,9 @@ class Org(Resource): def __str__(self): return '' % getattr(self, 'login', '') + + +class Team(Resource): + + def __str__(self): + return '' % getattr(self, 'name', '') diff --git a/pygithub3/resources/repos.py b/pygithub3/resources/repos.py index c7ec5e8..f50d694 100644 --- a/pygithub3/resources/repos.py +++ b/pygithub3/resources/repos.py @@ -18,12 +18,6 @@ def __str__(self): return '' % getattr(self, 'name', '') -class Team(Resource): - - def __str__(self): - return '' % getattr(self, 'name', '') - - class Author(Resource): _dates = ('date') @@ -115,6 +109,7 @@ def ball_to_upload(self): 'Policy': self.policy, 'Signature': self.signature, 'Content-Type': self.mime_type}) + class Hook(Resource): _dates = ('created_at', 'pushed_at') From 8e91d748c250e208be090396e4b9434f22221107 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 24 Apr 2012 01:20:26 +0200 Subject: [PATCH 17/78] name -> org --- pygithub3/requests/orgs/__init__.py | 4 ++-- pygithub3/services/orgs/__init__.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pygithub3/requests/orgs/__init__.py b/pygithub3/requests/orgs/__init__.py index a490a17..deba5ef 100644 --- a/pygithub3/requests/orgs/__init__.py +++ b/pygithub3/requests/orgs/__init__.py @@ -14,12 +14,12 @@ def clean_uri(self): class Get(Request): - uri = 'orgs/{name}' + uri = 'orgs/{org}' resource = Org class Update(Request): - uri = 'orgs/{name}' + uri = 'orgs/{org}' resource = Org body_schema = { 'schema': ('billing_email', 'company', 'email', 'location', 'name'), diff --git a/pygithub3/services/orgs/__init__.py b/pygithub3/services/orgs/__init__.py index 28f6b16..c222956 100644 --- a/pygithub3/services/orgs/__init__.py +++ b/pygithub3/services/orgs/__init__.py @@ -25,18 +25,18 @@ def list(self, user=None): request = self.request_builder('orgs.list', user=user) return self._get_result(request) - def get(self, name): + def get(self, org): """ Get a single org - :param str name: Org name + :param str org: Org name """ - request = self.request_builder('orgs.get', name=name) + request = self.request_builder('orgs.get', org=org) return self._get(request) - def update(self, name, data): + def update(self, org, data): """ Update a single org - :param str name: Org name + :param str org: Org name :param dict data: Input. See `github orgs doc`_ .. warning :: @@ -47,5 +47,5 @@ def update(self, name, data): org_service.update(dict(company='ACME Development', location='Timbuctoo')) """ - request = self.request_builder('orgs.update', name=name, body=data) + request = self.request_builder('orgs.update', org=org, body=data) return self._patch(request) From 5fa7037bf460d2936c935f19968239d694378f51 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 24 Apr 2012 08:44:11 +0200 Subject: [PATCH 18/78] Add Orgs Members API --- pygithub3/requests/orgs/members.py | 34 ++++++++++ pygithub3/resources/orgs.py | 6 ++ pygithub3/services/orgs/__init__.py | 5 ++ pygithub3/services/orgs/members.py | 91 +++++++++++++++++++++++++++ pygithub3/tests/services/test_orgs.py | 55 ++++++++++++++-- 5 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 pygithub3/requests/orgs/members.py create mode 100644 pygithub3/services/orgs/members.py diff --git a/pygithub3/requests/orgs/members.py b/pygithub3/requests/orgs/members.py new file mode 100644 index 0000000..4596eb3 --- /dev/null +++ b/pygithub3/requests/orgs/members.py @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.resources.orgs import Member +from . import Request + + +class List(Request): + uri = 'orgs/{org}/members' + resource = Member + + +class Get(Request): + uri = 'orgs/{org}/members/{user}' + + +class Delete(Request): + uri = 'orgs/{org}/members/{user}' + + +class Listpublic(Request): + uri = 'orgs/{org}/public_members' + resource = Member + + +class Getpublic(Request): + uri = 'orgs/{org}/public_members/{user}' + + +class Publicize(Request): + uri = 'orgs/{org}/public_members/{user}' + + +class Conceal(Request): + uri = 'orgs/{org}/public_members/{user}' diff --git a/pygithub3/resources/orgs.py b/pygithub3/resources/orgs.py index a79395d..6e2d39b 100644 --- a/pygithub3/resources/orgs.py +++ b/pygithub3/resources/orgs.py @@ -18,3 +18,9 @@ class Team(Resource): def __str__(self): return '' % getattr(self, 'name', '') + + +class Member(Resource): + + def __str__(self): + return '' % getattr(self, 'login', '') diff --git a/pygithub3/services/orgs/__init__.py b/pygithub3/services/orgs/__init__.py index c222956..6a75081 100644 --- a/pygithub3/services/orgs/__init__.py +++ b/pygithub3/services/orgs/__init__.py @@ -1,11 +1,16 @@ # -*- encoding: utf-8 -*- from pygithub3.services.base import Service +from .members import Members class Org(Service): """ Consume `Orgs API `_ """ + def __init__(self, **config): + self.members = Members(**config) + super(Org, self).__init__(**config) + def list(self, user=None): """ Get user's orgs diff --git a/pygithub3/services/orgs/members.py b/pygithub3/services/orgs/members.py new file mode 100644 index 0000000..3df4dab --- /dev/null +++ b/pygithub3/services/orgs/members.py @@ -0,0 +1,91 @@ +# -*- encoding: utf-8 -*- + +from . import Service + + +class Members(Service): + """ Consume `Members API `_ + """ + + def list(self, org): + """ Get org's members + + :param str org: Organisation name + :returns: A :doc:`result` + + If you call it authenticated, and are a member of the org, public and + private members will be visible. + + If not, only public members will be visible. + """ + request = self.request_builder('orgs.members.list', org=org) + return self._get_result(request) + + def is_member(self, org, user): + """ Determine if user is a member of org + + :param str org: Organisation name + :param str user: User name + """ + request = self.request_builder('orgs.members.get', org=org, user=user) + return self._bool(request) + + def remove_member(self, org, user): + """ Remove user from all teams in org + + :param str org: Organisation name + :param str user: User name + + .. warning :: + You must be authenticated and an owner of org + + """ + request = self.request_builder('orgs.members.get', org=org, user=user) + return self._delete(request) + + def list_public(self, org): + """ Get org's public members + + :param str org: Organisation name + :returns: A :doc:`result` + """ + request = self.request_builder('orgs.members.listpublic', org=org) + return self._get_result(request) + + def is_public_member(self, org, user): + """ Determine if user is a public member of org + + :param str org: Organisation name + :param str user: User name + """ + request = self.request_builder('orgs.members.getpublic', + org=org, user=user) + return self._bool(request) + + def publicize_membership(self, org, user): + """ Publicize user's membership in org + + :param str org: Organisation name + :param str user: User name + + .. warning :: + You must be authenticated and the user, or an owner of the org + + """ + request = self.request_builder('orgs.members.publicize', + org=org, user=user) + return self._put(request) + + def conceal_membership(self, org, user): + """ Conceal user's membership in org + + :param str org: Organisation name + :param str user: User name + + .. warning :: + You must be authenticated and the user, or an owner of the org + + """ + request = self.request_builder('orgs.members.conceal', + org=org, user=user) + return self._delete(request) diff --git a/pygithub3/tests/services/test_orgs.py b/pygithub3/tests/services/test_orgs.py index a6416ff..e741666 100644 --- a/pygithub3/tests/services/test_orgs.py +++ b/pygithub3/tests/services/test_orgs.py @@ -6,7 +6,7 @@ from pygithub3.tests.utils.core import TestCase from pygithub3.resources.base import json -from pygithub3.services.orgs import Org +from pygithub3.services.orgs import Org, Members from pygithub3.tests.utils.base import (mock_response, mock_response_result, mock_json) from pygithub3.tests.utils.services import _ @@ -17,7 +17,6 @@ @patch.object(requests.sessions.Session, 'request') class TestOrgService(TestCase): - def setUp(self): self.org = Org() @@ -30,7 +29,7 @@ def test_LIST_with_user(self, request_method): request_method.return_value = mock_response_result() self.org.list('octocat').all() self.assertEqual(request_method.call_args[0], - ('get', _('users/octocat/orgs'))) + ('get', _('users/octocat/orgs'))) def test_GET(self, request_method): request_method.return_value = mock_response() @@ -41,4 +40,52 @@ def test_UPDATE(self, request_method): request_method.return_value = mock_response('patch') self.org.update('acme', {'company': 'ACME Widgets'}) self.assertEqual(request_method.call_args[0], - ('patch', _('orgs/acme'))) + ('patch', _('orgs/acme'))) + + +@patch.object(requests.sessions.Session, 'request') +class TestOrgMemberService(TestCase): + def setUp(self): + self.ms = Members() + + def test_LIST(self, request_method): + request_method.return_value = mock_response_result() + self.ms.list('acme').all() + self.assertEqual(request_method.call_args[0], + ('get', _('orgs/acme/members'))) + + def test_IS_MEMBER(self, request_method): + request_method.return_value = mock_response() + self.ms.is_member('acme', 'octocat') + self.assertEqual(request_method.call_args[0], + ('head', _('orgs/acme/members/octocat'))) + + def test_REMOVE_MEMBER(self, request_method): + request_method.return_value = mock_response('delete') + self.ms.remove_member('acme', 'octocat') + self.assertEqual(request_method.call_args[0], + ('delete', _('orgs/acme/members/octocat'))) + + def test_LIST_PUBLIC(self, request_method): + request_method.return_value = mock_response_result() + self.ms.list_public('acme').all() + self.assertEqual(request_method.call_args[0], + ('get', _('orgs/acme/public_members'))) + + def test_IS_PUBLIC_MEMBER(self, request_method): + request_method.return_value = mock_response() + self.ms.is_public_member('acme', 'octocat') + self.assertEqual(request_method.call_args[0], + ('head', _('orgs/acme/public_members/octocat'))) + + def test_PUBLICIZE_MEMBERSHIP(self, request_method): + request_method.return_value = mock_response() + self.ms.publicize_membership('acme', 'octocat') + self.assertEqual(request_method.call_args[0], + ('put', _('orgs/acme/public_members/octocat'))) + + def test_CONCEAL_MEMBERSHIP(self, request_method): + request_method.return_value = mock_response('delete') + self.ms.conceal_membership('acme', 'octocat') + self.assertEqual(request_method.call_args[0], + ('delete', _('orgs/acme/public_members/octocat'))) From b488b8447fc943d920cd72b60c6647e5840ff800 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 24 Apr 2012 09:10:06 +0200 Subject: [PATCH 19/78] An empty string doesn't work around the 411 issue on PUTs. Use 'PLACEHOLDER' --- pygithub3/services/base.py | 10 +++++----- pygithub3/tests/services/test_core.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pygithub3/services/base.py b/pygithub3/services/base.py index 886a666..c91bc72 100644 --- a/pygithub3/services/base.py +++ b/pygithub3/services/base.py @@ -105,10 +105,10 @@ def _patch(self, request, **kwargs): def _put(self, request, **kwargs): """ Bug in Github API? requests library? - I must send data as empty string when the specifications' of some PUT - request are 'Not send input data'. If I don't do that and send data as - None, the requests library doesn't send 'Content-length' header and the - server returns 411 - Required Content length (at least 0) + I must send data when the specifications' of some PUT request are 'Not + send input data'. If I don't do that and send data as None, the + requests library doesn't send 'Content-length' header and the server + returns 411 - Required Content length (at least 0) For instance: - follow-user request doesn't send input data @@ -119,7 +119,7 @@ def _put(self, request, **kwargs): Related: https://github.com/github/developer.github.com/pull/52 """ - input_data = request.get_body() or '' + input_data = request.get_body() or 'PLACEHOLDER' response = self._client.put(request, data=input_data, **kwargs) if response.status_code != 204: # != NO_CONTENT return request.resource.loads(response.content) diff --git a/pygithub3/tests/services/test_core.py b/pygithub3/tests/services/test_core.py index bd95b34..8a2bbbe 100644 --- a/pygithub3/tests/services/test_core.py +++ b/pygithub3/tests/services/test_core.py @@ -27,7 +27,7 @@ def test_BOOL(self, request_method): def test_PUT(self, request_method): self.s._put(self.r, **self.args) - data = '' # See _put + data = 'PLACEHOLDER' # See _put request_method.assert_called_with('put', _('dummyrequest'), data=data, params=self.args) From 52b4a5db0be700fe6b48bfe1e0bda2bd86294db7 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 24 Apr 2012 10:42:25 +0200 Subject: [PATCH 20/78] Add Teams Service --- pygithub3/requests/orgs/teams.py | 71 +++++++++++++ pygithub3/services/orgs/__init__.py | 2 + pygithub3/services/orgs/teams.py | 147 ++++++++++++++++++++++++++ pygithub3/tests/services/test_orgs.py | 83 ++++++++++++++- 4 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 pygithub3/requests/orgs/teams.py create mode 100644 pygithub3/services/orgs/teams.py diff --git a/pygithub3/requests/orgs/teams.py b/pygithub3/requests/orgs/teams.py new file mode 100644 index 0000000..4b30533 --- /dev/null +++ b/pygithub3/requests/orgs/teams.py @@ -0,0 +1,71 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.resources.orgs import Member, Team +from pygithub3.resources.repos import Repo +from . import Request + + +class List(Request): + uri = 'orgs/{org}/teams' + resource = Team + + +class Get(Request): + uri = 'teams/{id}' + resource = Team + + +class Create(Request): + uri = 'orgs/{org}/teams' + resource = Team + body_schema = { + 'schema': ('name', 'repo_names', 'permission',), + 'required': ('name',), + } + + +class Update(Request): + uri = 'teams/{id}' + resource = Team + body_schema = { + 'schema': ('name', 'permission',), + 'required': ('name',), + } + + +class Delete(Request): + uri = 'teams/{id}' + + +class List_members(Request): + uri = 'teams/{id}/members' + resource = Member + + +class Is_member(Request): + uri = 'teams/{id}/members/{user}' + + +class Add_member(Request): + uri = 'teams/{id}/members/{user}' + + +class Remove_member(Request): + uri = 'teams/{id}/members/{user}' + + +class List_repos(Request): + uri = 'teams/{id}/repos' + resource = Repo + + +class Contains_repo(Request): + uri = 'teams/{id}/repos/{user}/{repo}' + + +class Add_repo(Request): + uri = 'teams/{id}/repos/{user}/{repo}' + + +class Remove_repo(Request): + uri = 'teams/{id}/repos/{user}/{repo}' diff --git a/pygithub3/services/orgs/__init__.py b/pygithub3/services/orgs/__init__.py index 6a75081..8499719 100644 --- a/pygithub3/services/orgs/__init__.py +++ b/pygithub3/services/orgs/__init__.py @@ -2,6 +2,7 @@ from pygithub3.services.base import Service from .members import Members +from .teams import Teams class Org(Service): @@ -9,6 +10,7 @@ class Org(Service): def __init__(self, **config): self.members = Members(**config) + self.teams = Teams(**config) super(Org, self).__init__(**config) def list(self, user=None): diff --git a/pygithub3/services/orgs/teams.py b/pygithub3/services/orgs/teams.py new file mode 100644 index 0000000..2e47803 --- /dev/null +++ b/pygithub3/services/orgs/teams.py @@ -0,0 +1,147 @@ +# -*- encoding: utf-8 -*- + +from . import Service + + +class Teams(Service): + """ Consume `Teams API `_ + + .. warning :: + You must be authenticated as an owner of the org + """ + + def list(self, org): + """ Get org's teams + + :param str org: Organisation name + :returns: A :doc:`result` + """ + request = self.request_builder('orgs.teams.list', org=org) + return self._get_result(request) + + def get(self, id): + """ Get a team + + :param int id: The team id + :returns: A :doc:`result` + """ + request = self.request_builder('orgs.teams.get', id=id) + return self._get(request) + + def create(self, org, name, repo_names=None, permission=None): + """ Create a new team + + :param str org: Organisation name + :param str name: Team name + :param list repo_names: List of repo names to belong to the team + :param str permission: Permissions to be granted to members + """ + data = {'name': name} + if repo_names: + data['repo_names'] = repo_names + if permission: + data['permission'] = permission + request = self.request_builder('orgs.teams.create', org=org, body=data) + return self._post(request) + + def update(self, id, name, permission=None): + """ Update a team + + :param int id: The team id + :param str name: Team name + :param str permission: Permissions to be granted to members + """ + data = {'name': name} + if permission: + data['permission'] = permission + request = self.request_builder('orgs.teams.update', id=id, body=data) + return self._patch(request) + + def delete(self, id): + """ Delete a team + + :param int id: The team id + """ + request = self.request_builder('orgs.teams.delete', id=id) + return self._delete(request) + + def list_members(self, id): + """ List the members of a team + + :param int id: The team id + :returns: A :doc:`result` + """ + request = self.request_builder('orgs.teams.list_members', id=id) + return self._get_result(request) + + def is_member(self, id, user): + """ Determine if user is a member of a team + + :param int id: The team id + :param str user: User name + """ + request = self.request_builder('orgs.teams.is_member', + id=id, user=user) + return self._bool(request) + + def add_member(self, id, user): + """ Add a user to a team + + :param int id: The team id + :param str user: User name + """ + request = self.request_builder('orgs.teams.add_member', + id=id, user=user) + return self._put(request) + + def remove_member(self, id, user): + """ Remove a member from a team + + :param int id: The team id + :param str user: User name + """ + request = self.request_builder('orgs.teams.remove_member', + id=id, user=user) + return self._delete(request) + + def list_repos(self, id): + """ List the repos that a team's members get access to + + :param int id: The team id + :returns: A :doc:`result` + """ + request = self.request_builder('orgs.teams.list_repos', id=id) + return self._get_result(request) + + def contains_repo(self, id, user, repo): + """ Determine if user is a member of a team + + :param int id: The team id + :param str user: User name + :param str repo: Repo name + """ + request = self.request_builder('orgs.teams.contains_repo', + id=id, user=user, repo=repo) + return self._bool(request) + + def add_repo(self, id, user, repo): + """ Give team members access to a repo + + :param int id: The team id + :param str user: User name + :param str repo: Repo name + """ + request = self.request_builder('orgs.teams.add_repo', + id=id, user=user, repo=repo) + return self._put(request) + + def remove_repo(self, id, user, repo): + """ Remove a repo from the a team + + :param int id: The team id + :param str user: User name + :param str repo: Repo name + """ + request = self.request_builder('orgs.teams.remove_repo', + id=id, user=user, repo=repo) + return self._delete(request) diff --git a/pygithub3/tests/services/test_orgs.py b/pygithub3/tests/services/test_orgs.py index e741666..4d4c1c9 100644 --- a/pygithub3/tests/services/test_orgs.py +++ b/pygithub3/tests/services/test_orgs.py @@ -6,7 +6,7 @@ from pygithub3.tests.utils.core import TestCase from pygithub3.resources.base import json -from pygithub3.services.orgs import Org, Members +from pygithub3.services.orgs import Org, Members, Teams from pygithub3.tests.utils.base import (mock_response, mock_response_result, mock_json) from pygithub3.tests.utils.services import _ @@ -89,3 +89,84 @@ def test_CONCEAL_MEMBERSHIP(self, request_method): self.ms.conceal_membership('acme', 'octocat') self.assertEqual(request_method.call_args[0], ('delete', _('orgs/acme/public_members/octocat'))) + + +@patch.object(requests.sessions.Session, 'request') +class TestOrgMemberService(TestCase): + def setUp(self): + self.ts = Teams() + + def test_LIST(self, request_method): + request_method.return_value = mock_response_result() + self.ts.list('acme').all() + self.assertEqual(request_method.call_args[0], + ('get', _('orgs/acme/teams'))) + + def test_GET(self, request_method): + request_method.return_value = mock_response_result() + self.ts.get(1) + self.assertEqual(request_method.call_args[0], ('get', _('teams/1'))) + + def test_CREATE(self, request_method): + request_method.return_value = mock_response_result('post') + self.ts.create('acme', 'committers') + self.assertEqual(request_method.call_args[0], + ('post', _('orgs/acme/teams'))) + + def test_UPDATE(self, request_method): + request_method.return_value = mock_response_result() + self.ts.update(1, 'committers', 'push') + self.assertEqual(request_method.call_args[0], ('patch', _('teams/1'))) + + def test_DELETE(self, request_method): + request_method.return_value = mock_response_result('delete') + self.ts.delete(1) + self.assertEqual(request_method.call_args[0], ('delete', _('teams/1'))) + + def test_LIST_MEMBERS(self, request_method): + request_method.return_value = mock_response_result() + self.ts.list_members(1).all() + self.assertEqual(request_method.call_args[0], + ('get', _('teams/1/members'))) + + def test_IS_MEMBER(self, request_method): + request_method.return_value = mock_response_result() + self.ts.is_member(1, 'octocat') + self.assertEqual(request_method.call_args[0], + ('head', _('teams/1/members/octocat'))) + + def test_ADD_MEMBER(self, request_method): + request_method.return_value = mock_response_result() + self.ts.add_member(1, 'octocat') + self.assertEqual(request_method.call_args[0], + ('put', _('teams/1/members/octocat'))) + + def test_REMOVE_MEMBER(self, request_method): + request_method.return_value = mock_response_result('delete') + self.ts.remove_member(1, 'octocat') + self.assertEqual(request_method.call_args[0], + ('delete', _('teams/1/members/octocat'))) + + def test_LIST_REPOS(self, request_method): + request_method.return_value = mock_response_result() + self.ts.list_repos(1).all() + self.assertEqual(request_method.call_args[0], + ('get', _('teams/1/repos'))) + + def test_CONTAINS_REPO(self, request_method): + request_method.return_value = mock_response_result() + self.ts.contains_repo(1, 'octocat', 're_oct') + self.assertEqual(request_method.call_args[0], + ('head', _('teams/1/repos/octocat/re_oct'))) + + def test_ADD_TEAM_REPO(self, request_method): + request_method.return_value = mock_response_result() + self.ts.add_repo(1, 'octocat', 're_oct') + self.assertEqual(request_method.call_args[0], + ('put', _('teams/1/repos/octocat/re_oct'))) + + def test_REMOVE_TEAM_REPO(self, request_method): + request_method.return_value = mock_response_result('delete') + self.ts.remove_repo(1, 'octocat', 're_oct') + self.assertEqual(request_method.call_args[0], + ('delete', _('teams/1/repos/octocat/re_oct'))) From bc394f6bcb642ba2c052cb6ebcaaacd67c262a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Thu, 26 Apr 2012 10:41:29 +0200 Subject: [PATCH 21/78] separate requirements for users and developers --- requirements.txt => requirements/base.txt | 0 requirements/dev.txt | 4 ++++ 2 files changed, 4 insertions(+) rename requirements.txt => requirements/base.txt (100%) create mode 100644 requirements/dev.txt diff --git a/requirements.txt b/requirements/base.txt similarity index 100% rename from requirements.txt rename to requirements/base.txt diff --git a/requirements/dev.txt b/requirements/dev.txt new file mode 100644 index 0000000..2004d29 --- /dev/null +++ b/requirements/dev.txt @@ -0,0 +1,4 @@ +-r base.txt + +nose +mock From 34ec5545901669513c80b007a79419ab0a255480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Thu, 26 Apr 2012 10:42:22 +0200 Subject: [PATCH 22/78] add Makefile --- .gitignore | 2 ++ Makefile | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 Makefile diff --git a/.gitignore b/.gitignore index 228a223..269c735 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ *.rope* MANIFEST docs/_build +dist/ +*egg-info diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f2b3997 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +init: + pip install -r requirements/dev.txt From a136c8a6496df445c7c00dfc97af17391b294245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Thu, 26 Apr 2012 11:09:44 +0200 Subject: [PATCH 23/78] `setup.py` modified for finding requirements --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9052973..ad082b9 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ long_description=open('README.rst').read(), license='ISC', packages=find_packages(exclude=['*tests*']), - install_requires=map(str.strip, open('requirements.txt')), + install_requires=map(str.strip, open('requirements/base.txt')), include_package_data=True, classifiers=( 'Programming Language :: Python', From 97564bccb7d164cd6e55b5651fda8e2cc663d39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Sat, 28 Apr 2012 00:46:33 +0200 Subject: [PATCH 24/78] add `updated_at` to `_dates` in Repo resource --- pygithub3/resources/repos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygithub3/resources/repos.py b/pygithub3/resources/repos.py index c7ec5e8..efd5b13 100644 --- a/pygithub3/resources/repos.py +++ b/pygithub3/resources/repos.py @@ -10,7 +10,7 @@ class Repo(Resource): - _dates = ('created_at', 'pushed_at') + _dates = ('created_at', 'updated_at', 'pushed_at') _maps = {'owner': User, 'organization': Org, 'parent': 'self', 'source': 'self'} From 1b1ccd6b86740157b2529614945a218d73049f4a Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 28 Apr 2012 00:19:49 +0200 Subject: [PATCH 25/78] Litle fixs --- pygithub3/requests/git_data/commits.py | 2 +- pygithub3/requests/git_data/references.py | 2 +- pygithub3/requests/git_data/trees.py | 6 +- pygithub3/services/base.py | 7 +++ pygithub3/services/git_data/blobs.py | 14 +++-- pygithub3/services/git_data/commits.py | 19 ++++--- pygithub3/services/git_data/references.py | 69 +++++++++++++---------- pygithub3/services/git_data/tags.py | 26 +++++---- pygithub3/services/git_data/trees.py | 28 +++++---- pygithub3/tests/services/test_git_data.py | 13 +---- 10 files changed, 101 insertions(+), 85 deletions(-) diff --git a/pygithub3/requests/git_data/commits.py b/pygithub3/requests/git_data/commits.py index caf1e7d..a26e07d 100644 --- a/pygithub3/requests/git_data/commits.py +++ b/pygithub3/requests/git_data/commits.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- from pygithub3.requests.base import Request -from pygithub3.resources.git_data import Commit +from pygithub3.resources.repos import Commit class Get(Request): diff --git a/pygithub3/requests/git_data/references.py b/pygithub3/requests/git_data/references.py index 99cf41a..2aac6a4 100644 --- a/pygithub3/requests/git_data/references.py +++ b/pygithub3/requests/git_data/references.py @@ -8,7 +8,7 @@ class Get(Request): class List(Request): - uri = 'repos/{user}/{repo}/git/refs' + uri = 'repos/{user}/{repo}/git/refs/{namespace}' resource = Reference diff --git a/pygithub3/requests/git_data/trees.py b/pygithub3/requests/git_data/trees.py index bd1593f..11d6409 100644 --- a/pygithub3/requests/git_data/trees.py +++ b/pygithub3/requests/git_data/trees.py @@ -6,15 +6,11 @@ class Get(Request): uri = 'repos/{user}/{repo}/git/trees/{sha}' resource = Tree - def clean_uri(self): - if self.recursive: - return self.uri + '?recursive=1' - class Create(Request): uri = 'repos/{user}/{repo}/git/trees' resource = Tree body_schema = { - 'schema': ('tree',), + 'schema': ('tree', 'base_tree'), 'required': ('tree',), } diff --git a/pygithub3/services/base.py b/pygithub3/services/base.py index 886a666..1059371 100644 --- a/pygithub3/services/base.py +++ b/pygithub3/services/base.py @@ -80,6 +80,12 @@ def set_token(self, token): """ self._client.set_token(token) + #TODO: Refact as decorator:: + """ + Reason: make_request and request_builder ... are confusing names + @precedence('user') + def list(self, sha, user=None): + """ def make_request(self, request, **kwargs): if 'user' in kwargs: kwargs['user'] = kwargs['user'] or self.get_user() @@ -146,6 +152,7 @@ def _get_normal_result(self, request, **kwargs): return normal.Result(method) +# XXX: Refact to set_ method class MimeTypeMixin(object): """ Mimetype support to Services diff --git a/pygithub3/services/git_data/blobs.py b/pygithub3/services/git_data/blobs.py index 4f1a6e7..f6a2ff3 100644 --- a/pygithub3/services/git_data/blobs.py +++ b/pygithub3/services/git_data/blobs.py @@ -1,10 +1,10 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from pygithub3.services.base import Service +from pygithub3.services.base import Service, MimeTypeMixin -class Blobs(Service): +class Blobs(Service, MimeTypeMixin): """Consume `Blobs API `_""" def get(self, sha, user=None, repo=None): @@ -14,10 +14,12 @@ def get(self, sha, user=None, repo=None): :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ request = self.make_request('git_data.blobs.get', sha=sha, - user=user, repo=repo) - return self._get(request) + user=user, repo=repo) + return self._get(request, **self._get_mimetype_as_header()) def create(self, data, user=None, repo=None): """Create a blob @@ -26,7 +28,9 @@ def create(self, data, user=None, repo=None): :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ request = self.make_request('git_data.blobs.create', body=data, - user=user, repo=repo) + user=user, repo=repo) return self._post(request) diff --git a/pygithub3/services/git_data/commits.py b/pygithub3/services/git_data/commits.py index 25e8775..ddeed98 100644 --- a/pygithub3/services/git_data/commits.py +++ b/pygithub3/services/git_data/commits.py @@ -10,24 +10,27 @@ class Commits(Service): def get(self, sha, user=None, repo=None): """get a commit from the current repo - :param str sha: SHA of the Commit that you want. + :param str sha: SHA of the Commit that you want :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ request = self.make_request('git_data.commits.get', sha=sha, - user=user, repo=repo) + user=user, repo=repo) return self._get(request) def create(self, data, user=None, repo=None): """create a commit on a repo :param dict data: Input. See `github commits doc`_ - :param str user: username - :param str repo: repository name + :param str user: Username + :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._post( - self.make_request('git_data.commits.create', user=user, repo=repo, - body=data) - ) + request = self.make_request('git_data.commits.create', user=user, + repo=repo, body=data) + return self._post(request) diff --git a/pygithub3/services/git_data/references.py b/pygithub3/services/git_data/references.py index 3a24f90..0b46062 100644 --- a/pygithub3/services/git_data/references.py +++ b/pygithub3/services/git_data/references.py @@ -8,60 +8,66 @@ class References(Service): """Consume `References API `_""" def get(self, ref, user=None, repo=None): - """Get a reference. + """ Get a reference - .. note:: - Remember that branch references look like "heads/" - - :param str ref: the name of the reference to get + :param str ref: The name of the reference to get :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` + + .. note:: + Remember that branch references look like "heads/" """ - return self._get( - self.make_request('git_data.references.get', ref=ref, user=user, - repo=repo) - ) + request = self.make_request('git_data.references.get', ref=ref, + user=user, repo=repo) + return self._get(request) def list(self, namespace='', user=None, repo=None): - """List all the references + """ List all the references :param str namespace: Limit the request to a particular type of reference. For example, ``heads`` or ``tags``. :param str user: Username :param str repo: Repository + :returns: A :doc:`result` + .. note:: + Remember :ref:`config precedence` """ - return self._get( - self.make_request('git_data.references.list', user=user, repo=repo) - ) + request = self.make_request('git_data.references.list', user=user, + repo=repo, namespace=namespace) + return self._get_result(request) - def create(self, body, user=None, repo=None): - """Create a reference + def create(self, data, user=None, repo=None): + """ Create a reference - :param dict body: Data describing the reference to create + :param dict data: Input. See `github refs doc`_ :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._post( - self.make_request('git_data.references.create', body=body, - user=user, repo=repo) - ) + request = self.make_request('git_data.references.create', body=data, + user=user, repo=repo) + return self._post(request) - def update(self, ref, body, user=None, repo=None): - """Update an existing reference + def update(self, ref, data, user=None, repo=None): + """ Update an existing reference :param str ref: The SHA of the reference to update - :param dict body: Data to update the reference with + :param dict data: Input. See `github refs doc`_ :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._patch( - self.make_request('git_data.references.update', ref=ref, body=body, - user=user, repo=repo) - ) + request = self.make_request('git_data.references.update', ref=ref, + body=data, user=user, repo=repo) + return self._patch(request) def delete(self, ref, user=None, repo=None): """Delete a reference @@ -70,8 +76,9 @@ def delete(self, ref, user=None, repo=None): :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._delete( - self.make_request('git_data.references.delete', ref=ref, user=user, - repo=repo) - ) + request = self.make_request('git_data.references.delete', ref=ref, + user=user, repo=repo) + return self._delete(request) diff --git a/pygithub3/services/git_data/tags.py b/pygithub3/services/git_data/tags.py index 03d38ac..337f6f0 100644 --- a/pygithub3/services/git_data/tags.py +++ b/pygithub3/services/git_data/tags.py @@ -8,27 +8,29 @@ class Tags(Service): """Consume `Tags API `_""" def get(self, sha, user=None, repo=None): - """Get a tag + """ Get a tag :param str sha: The sha of the tag to get. :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._get( - self.make_request('git_data.tags.get', sha=sha, user=user, - repo=repo) - ) + request = self.make_request('git_data.tags.get', sha=sha, user=user, + repo=repo) + return self._get(request) - def create(self, body, user=None, repo=None): - """Create a tag + def create(self, data, user=None, repo=None): + """ Create a tag - :param dict body: Data describing the tag to create + :param dict data: Input. See `github tags doc`_ :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._post( - self.make_request('git_data.tags.create', body=body, user=user, - repo=repo) - ) + request = self.make_request('git_data.tags.create', body=data, + user=user, repo=repo) + return self._post(request) diff --git a/pygithub3/services/git_data/trees.py b/pygithub3/services/git_data/trees.py index 00e010b..6032e74 100644 --- a/pygithub3/services/git_data/trees.py +++ b/pygithub3/services/git_data/trees.py @@ -8,7 +8,7 @@ class Trees(Service): """Consume `Trees API `_""" def get(self, sha, recursive=False, user=None, repo=None): - """Get a tree object + """ Get a tree object :param str sha: The SHA of the tree you want. :param bool recursive: Whether to resolve each sub-tree belonging to @@ -16,19 +16,23 @@ def get(self, sha, recursive=False, user=None, repo=None): :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._get( - self.make_request('git_data.trees.get', sha=sha, - recursive=recursive, user=user, repo=repo) - ) + request = self.make_request('git_data.trees.get', sha=sha, user=user, + repo=repo) + return self._get(request, recursive=recursive) - def create(self, body, user=None, repo=None): - """Create a tree object + def create(self, data, user=None, repo=None): + """ Create a tree object - :param dict body: Data describing the tree to create + :param dict data: Input. See `github trees doc`_ + :param str user: Username + :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._post( - self.make_request('git_data.trees.create', body=body, user=user, - repo=repo) - ) + request = self.make_request('git_data.trees.create', body=data, + user=user, repo=repo) + return self._post(request) diff --git a/pygithub3/tests/services/test_git_data.py b/pygithub3/tests/services/test_git_data.py index 45ef6d1..da1abf7 100644 --- a/pygithub3/tests/services/test_git_data.py +++ b/pygithub3/tests/services/test_git_data.py @@ -61,6 +61,7 @@ def test_CREATE(self, reqm): ('post', _('repos/octocat/repo/git/commits')) ) + @patch.object(requests.sessions.Session, 'request') class TestReferencesService(TestCase): def setUp(self): @@ -75,8 +76,8 @@ def test_GET(self, reqm): ) def test_LIST(self, reqm): - reqm.return_value = mock_response() - self.service.list() + reqm.return_value = mock_response_result() + self.service.list().all() self.assertEqual( reqm.call_args[0], ('get', _('repos/user/repo/git/refs')) @@ -142,14 +143,6 @@ def test_GET(self, reqm): ('get', _('repos/user/repo/git/trees/abc123')) ) - def test_GET_recursive(self, reqm): - reqm.return_value = mock_response() - self.service.get('abc123', recursive=True) - self.assertEqual( - reqm.call_args[0], - ('get', _('repos/user/repo/git/trees/abc123?recursive=1')) - ) - def test_CREATE(self, reqm): reqm.return_value = mock_response('post') self.service.create({ From 8f1b051b465dca03e0232d48e50321e7689e63ce Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 28 Apr 2012 23:36:00 +0200 Subject: [PATCH 26/78] New install environment to prod and dev Also fix some docs --- README.rst | 6 ++---- docs/git_data.rst | 9 +++++++-- docs/installation.rst | 4 ++-- requirements/dev.txt | 2 ++ test_requirements.txt | 4 ---- 5 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 test_requirements.txt diff --git a/README.rst b/README.rst index 1591e38..6dfe6d4 100644 --- a/README.rst +++ b/README.rst @@ -57,11 +57,9 @@ Contribute 2. Write a test to cover new feature or to reproduce bug 3. Code with `pep8 `_ rules 4. Add yourself to ``AUTHORS`` -5. Pull request it +5. Pull request it to ``develop`` branch Tests ----- -Test and docs requirements are listed in ``test_requirements.txt``. -Run ``pip install -r test_requirements.txt`` to install them and ``nosetests`` -to run tests. +Run ``make init`` to install test requirements and ``nosetests`` to run tests. diff --git a/docs/git_data.rst b/docs/git_data.rst index 549420b..9045841 100644 --- a/docs/git_data.rst +++ b/docs/git_data.rst @@ -26,7 +26,7 @@ GitData .. attribute:: commits - :ref:`Commits service` + :ref:`Gitdata commits service` .. attribute:: references @@ -50,7 +50,7 @@ Blobs :members: -.. _Commits service: +.. _Gitdata commits service: Commits ------- @@ -84,3 +84,8 @@ Trees .. autoclass:: pygithub3.services.git_data.Trees :members: + +.. _github commits doc: http://developer.github.com/v3/git/commits +.. _github refs doc: http://developer.github.com/v3/git/refs +.. _github tags doc: http://developer.github.com/v3/git/tags +.. _github trees doc: http://developer.github.com/v3/git/trees diff --git a/docs/installation.rst b/docs/installation.rst index 6eccc95..0601a7e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -22,8 +22,8 @@ If you install ``pygithub3`` with ``pip`` all is done. This is the best option. Optional ......... -The test suite uses `nose`_, `mock`_, and `unittest2`_. Compiling the -documentation requires `sphinx`_. +The test suite uses `nose`_, `mock`_, and `unittest2`_ (python 2.6). Compiling +the documentation requires `sphinx`_. Install all of these by running ``pip install -r test_requirements.txt``. Then just run ``nosetests`` to run the tests. diff --git a/requirements/dev.txt b/requirements/dev.txt index 2004d29..5b1f13b 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -2,3 +2,5 @@ nose mock +unittest2 +sphinx diff --git a/test_requirements.txt b/test_requirements.txt deleted file mode 100644 index 862d240..0000000 --- a/test_requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -nose -unittest2 -mock -sphinx From 3a82e76013624e931330e3726a22bf41088eb314 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 12 May 2012 17:28:52 +0200 Subject: [PATCH 27/78] Unify json imports --- pygithub3/core/errors.py | 6 +----- pygithub3/core/utils.py | 5 +++++ pygithub3/requests/base.py | 6 +----- pygithub3/resources/base.py | 5 +---- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pygithub3/core/errors.py b/pygithub3/core/errors.py index 4a95df0..9b84479 100644 --- a/pygithub3/core/errors.py +++ b/pygithub3/core/errors.py @@ -1,11 +1,7 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -try: - import simplejson as json -except ImportError: - import json - +from pygithub3.core.utils import json from pygithub3.exceptions import NotFound, BadRequest, UnprocessableEntity diff --git a/pygithub3/core/utils.py b/pygithub3/core/utils.py index c6dbf0a..24a3bbf 100644 --- a/pygithub3/core/utils.py +++ b/pygithub3/core/utils.py @@ -2,6 +2,11 @@ # -*- encoding: utf-8 -*- """ Utils to support python 2.6 compatibility """ +try: + import simplejson as json +except ImportError: + import json + from collections import MutableMapping diff --git a/pygithub3/requests/base.py b/pygithub3/requests/base.py index d3e18a3..13472b0 100644 --- a/pygithub3/requests/base.py +++ b/pygithub3/requests/base.py @@ -2,15 +2,11 @@ # -*- encoding: utf-8 -*- import re -try: - import simplejson as json -except ImportError: - import json +from pygithub3.core.utils import import_module, json from pygithub3.exceptions import (RequestDoesNotExist, UriInvalid, ValidationError, InvalidBodySchema) from pygithub3.resources.base import Raw -from pygithub3.core.utils import import_module ABS_IMPORT_PREFIX = 'pygithub3.requests' diff --git a/pygithub3/resources/base.py b/pygithub3/resources/base.py index 4ca2aa3..7045529 100644 --- a/pygithub3/resources/base.py +++ b/pygithub3/resources/base.py @@ -1,10 +1,7 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -try: - import simplejson as json -except ImportError: - import json +from pygithub3.core.utils import json class Resource(object): From cd7e1b88c9dfa5eaa126ece0a5529e224ae297d4 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 12 May 2012 17:30:05 +0200 Subject: [PATCH 28/78] Deleted 'validate_body' behaviour 'clean_body' had been developed to that functionality --- pygithub3/requests/base.py | 52 ++++++++++++--------------- pygithub3/tests/requests/test_core.py | 17 ++------- pygithub3/tests/utils/base.py | 13 +------ 3 files changed, 26 insertions(+), 56 deletions(-) diff --git a/pygithub3/requests/base.py b/pygithub3/requests/base.py index 13472b0..f49a7a9 100644 --- a/pygithub3/requests/base.py +++ b/pygithub3/requests/base.py @@ -12,12 +12,12 @@ class Body(object): + """ Input's request handler """ - def __init__(self, content, valid_body, validate_body=None): + def __init__(self, content, schema, required): self.content = content - self.schema = valid_body['schema'] - self.required = valid_body['required'] - self.validate_body = validate_body or (lambda x: None) + self.schema = schema + self.required = required def dumps(self): if not self.schema: @@ -25,12 +25,12 @@ def dumps(self): return json.dumps(self.parse()) def parse(self): + """ Parse body with schema-required rules """ if not hasattr(self.content, 'items'): raise ValidationError("'%s' needs a content dictionary" % self.__class__.__name__) parsed = dict([(key, self.content[key]) for key in self.schema if key in self.content]) - self.validate_body(parsed) for attr_required in self.required: if attr_required not in parsed: raise ValidationError("'%s' attribute is required" % @@ -42,22 +42,34 @@ def parse(self): class Request(object): - """ """ uri = '' resource = Raw body_schema = {} def __init__(self, **kwargs): - """ """ - self.body = kwargs.pop('body', None) + self.body = kwargs.pop('body', {}) self.args = kwargs self.clean() + def __getattr__(self, name): + return self.args.get(name) + + def __str__(self): + return self.populate_uri() + + def populate_uri(self): + try: + populated_uri = self.uri.format(**self.args) + except KeyError: + raise ValidationError( + "'%s' request wasn't be able to populate the uri '%s' with " + "'%s' args" % (self.__class__.__name__, self.uri, self.args)) + return str(populated_uri).strip('/') + def clean(self): self.uri = self.clean_uri() or self.uri - self.body = Body(self.clean_body(), self.clean_valid_body(), - self.validate_body) + self.body = Body(self.clean_body(), **self._clean_valid_body()) def clean_body(self): return self.body @@ -65,7 +77,7 @@ def clean_body(self): def clean_uri(self): return None - def clean_valid_body(self): + def _clean_valid_body(self): schema = set(self.body_schema.get('schema', ())) required = set(self.body_schema.get('required', ())) if not required.issubset(schema): @@ -75,27 +87,9 @@ def clean_valid_body(self): self.__class__.__name__, required, schema)) return dict(schema=schema, required=required) - def __getattr__(self, name): - return self.args.get(name) - - def __str__(self): - return self.populate_uri() - - def populate_uri(self): - try: - populated_uri = self.uri.format(**self.args) - except KeyError: - raise ValidationError( - "'%s' request wasn't be able to populate the uri '%s' with " - "'%s' args" % (self.__class__.__name__, self.uri, self.args)) - return str(populated_uri).strip('/') - def get_body(self): return self.body.dumps() - def validate_body(self, *args): - pass - class Factory(object): """ Request builder """ diff --git a/pygithub3/tests/requests/test_core.py b/pygithub3/tests/requests/test_core.py index 4632056..110f00e 100644 --- a/pygithub3/tests/requests/test_core.py +++ b/pygithub3/tests/requests/test_core.py @@ -2,14 +2,12 @@ # -*- encoding: utf-8 -*- from mock import Mock -from nose.tools import raises from pygithub3.tests.utils.core import TestCase from pygithub3.requests.base import Factory, Body, json, Request from pygithub3.exceptions import (UriInvalid, RequestDoesNotExist, ValidationError, InvalidBodySchema) -from pygithub3.tests.utils.base import (mock_json, DummyRequest, - DummyRequestValidation) +from pygithub3.tests.utils.base import mock_json, DummyRequest from pygithub3.tests.utils.requests import ( RequestWithArgs, RequestCleanedUri, RequestBodyInvalidSchema, RequestCleanedBody) @@ -76,7 +74,7 @@ class TestRequestBodyWithSchema(TestCase): def setUp(self): valid_body = dict(schema=('arg1', 'arg2'), required=('arg1', )) - self.b = Body({}, valid_body) + self.b = Body({}, **valid_body) def test_with_body_empty_and_schema_permissive(self): self.b.schema = ('arg1', 'arg2', '...') @@ -102,14 +100,3 @@ def test_with_body_as_None(self): def test_only_valid_keys(self): self.b.content = dict(arg1='arg1', arg2='arg2', fake='test') self.assertEqual(self.b.dumps(), dict(arg1='arg1', arg2='arg2')) - - -class TestBodyValidation(TestCase): - @raises(ValidationError) - def test_with_error(self): - req = DummyRequestValidation( - body={'foo': 'bar', 'error': 'yes'}, - ) - req.body_schema = {'schema': ('foo',), - 'required': ('foo',)} - req.get_body() diff --git a/pygithub3/tests/utils/base.py b/pygithub3/tests/utils/base.py index b3c3b76..f90b5d3 100644 --- a/pygithub3/tests/utils/base.py +++ b/pygithub3/tests/utils/base.py @@ -4,7 +4,7 @@ from mock import Mock from pygithub3.resources.base import Resource -from pygithub3.requests.base import Request, ValidationError +from pygithub3.requests.base import Request def mock_json(content): @@ -37,14 +37,3 @@ def loads_mock(content): class DummyRequest(Request): uri = 'dummyrequest' resource = DummyResource - - -class DummyRequestValidation(DummyRequest): - body_schema = { - 'schema': ('foo', 'error'), - 'required': ('foo',) - } - - def validate_body(self, body): - if body.get('error') == 'yes': - raise ValidationError('yes') From 29d43d19caa3c0eccfaff7005dd35d411b5dbc74 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 12 May 2012 17:46:06 +0200 Subject: [PATCH 29/78] Deleted 'dispatch' decorator. No sense --- pygithub3/requests/base.py | 41 ++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/pygithub3/requests/base.py b/pygithub3/requests/base.py index f49a7a9..c4fe5cc 100644 --- a/pygithub3/requests/base.py +++ b/pygithub3/requests/base.py @@ -106,28 +106,21 @@ def wrapper(self, request_uri, **kwargs): return func(self, request_uri.lower(), **kwargs) return wrapper - def dispatch(func): - def wrapper(self, request_uri, **kwargs): - module_chunk, s, request_chunk = request_uri.rpartition('.') - request_chunk = request_chunk.capitalize() - try: - # TODO: CamelCase and under_score support, now only Class Name - module = import_module('%s.%s' - % (ABS_IMPORT_PREFIX, module_chunk)) - request = getattr(module, request_chunk) - except ImportError: - raise RequestDoesNotExist("'%s' module does not exist" - % module_chunk) - except AttributeError: - raise RequestDoesNotExist( - "'%s' request does not exist in '%s' module" - % (request_chunk, module_chunk)) - return func(self, request, **kwargs) - return wrapper - @validate - @dispatch - def __call__(self, request='', **kwargs): - request = request(**kwargs) - assert isinstance(request, Request) - return request + def __call__(self, request_uri, **kwargs): + module_chunk, s, request_chunk = request_uri.rpartition('.') + request_chunk = request_chunk.capitalize() + try: + # TODO: CamelCase and under_score support, now only Class Name + module = import_module('%s.%s' % (ABS_IMPORT_PREFIX, module_chunk)) + request_class = getattr(module, request_chunk) + request = request_class(**kwargs) + assert isinstance(request, Request) + return request + except ImportError: + raise RequestDoesNotExist("'%s' module does not exist" + % module_chunk) + except AttributeError: + raise RequestDoesNotExist("'%s' request does not exist in " + "'%s' module" % (request_chunk, + module_chunk)) From 24a3ed5dcd2264a64e234ea7bd526049fafe7616 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 12 May 2012 19:04:23 +0200 Subject: [PATCH 30/78] Some fixes/typos and 'validate_body' related --- docs/pull_requests.rst | 8 ++-- pygithub3/core/client.py | 2 +- pygithub3/requests/pull_requests/__init__.py | 20 +++++----- pygithub3/requests/pull_requests/comments.py | 15 ++++--- pygithub3/resources/pull_requests.py | 2 + pygithub3/services/pull_requests/__init__.py | 40 ++++++++++++++----- pygithub3/services/pull_requests/comments.py | 31 +++++++++----- .../tests/services/test_pull_requests.py | 19 ++------- 8 files changed, 84 insertions(+), 53 deletions(-) diff --git a/docs/pull_requests.rst b/docs/pull_requests.rst index 40c3435..09313eb 100644 --- a/docs/pull_requests.rst +++ b/docs/pull_requests.rst @@ -7,11 +7,10 @@ Pull Requests service from pygithub3 import Github - gh = Github() + gh = Github(user='octocat', repo='sample') pull_requests = gh.pull_requests.list().all() - for pr in pull_requests: - commits = gh.pull_requests.list_commits(pr.number).all() + pull_request_commits = gh.pull_requests.list_commits(2512).all() Pull Requests ------------- @@ -31,3 +30,6 @@ Pull Request Comments .. autoclass:: pygithub3.services.pull_requests.Comments :members: + +.. _github pullrequests doc: http://developer.github.com/v3/pulls +.. _github pullrequests comments doc: http://developer.github.com/v3/pulls/comments diff --git a/pygithub3/core/client.py b/pygithub3/core/client.py index eadb18e..ee7c97f 100644 --- a/pygithub3/core/client.py +++ b/pygithub3/core/client.py @@ -81,7 +81,7 @@ def request(self, verb, request, **kwargs): def get(self, request, **kwargs): response = self.request('get', request, **kwargs) - # there are valid GET responses that != 200 + assert response.status_code == 200 return response def post(self, request, **kwargs): diff --git a/pygithub3/requests/pull_requests/__init__.py b/pygithub3/requests/pull_requests/__init__.py index bd03f3e..f25572d 100644 --- a/pygithub3/requests/pull_requests/__init__.py +++ b/pygithub3/requests/pull_requests/__init__.py @@ -1,5 +1,6 @@ +# -*- encoding: utf-8 -*- + from pygithub3.requests.base import Request, ValidationError -from pygithub3.resources.base import Raw from pygithub3.resources.pull_requests import PullRequest, File from pygithub3.resources.repos import Commit @@ -22,11 +23,12 @@ class Create(Request): 'required': ('base', 'head'), } - def validate_body(self, parsed): - if (not ('title' in parsed and 'body' in parsed) and - not 'issue' in parsed): + def clean_body(self): + if (not ('title' in self.body and 'body' in self.body) and + not 'issue' in self.body): raise ValidationError('pull request creation requires either an ' 'issue number or a title and body') + return self.body class Update(Request): uri = 'repos/{user}/{repo}/pulls/{number}' @@ -36,10 +38,12 @@ class Update(Request): 'required': (), } - def validate_body(self, body): - if 'state' in body and body['state'] not in ['open', 'closed']: + def clean_body(self): + if ('state' in self.body and + self.body['state'] not in ['open', 'closed']): raise ValidationError('If a state is specified, it must be one ' 'of "open" or "closed"') + return self.body class List_commits(Request): @@ -52,14 +56,12 @@ class List_files(Request): resource = File -class Merge_status(Request): +class Is_merged(Request): uri = 'repos/{user}/{repo}/pulls/{number}/merge' - resource = Raw class Merge(Request): uri = 'repos/{user}/{repo}/pulls/{number}/merge' - resource = Raw body_schema = { 'schema': ('commit_message',), 'required': (), diff --git a/pygithub3/requests/pull_requests/comments.py b/pygithub3/requests/pull_requests/comments.py index fa1e5b6..dd69894 100644 --- a/pygithub3/requests/pull_requests/comments.py +++ b/pygithub3/requests/pull_requests/comments.py @@ -1,4 +1,6 @@ -from pygithub3.requests.base import Request +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError from pygithub3.resources.pull_requests import Comment @@ -20,13 +22,14 @@ class Create(Request): 'required': ('body',), } - def validate_body(self, body): - if (not ('commit_id' in body and - 'path' in body and - 'position' in body) and - not 'in_reply_to' in body): + def clean_body(self): + if (not ('commit_id' in self.body and + 'path' in self.body and + 'position' in self.body) and + not 'in_reply_to' in self.body): raise ValidationError('supply either in_reply_to or commit_id, ' 'path, and position') + return self.body class Edit(Request): diff --git a/pygithub3/resources/pull_requests.py b/pygithub3/resources/pull_requests.py index 1fe2623..a28e0fe 100644 --- a/pygithub3/resources/pull_requests.py +++ b/pygithub3/resources/pull_requests.py @@ -1,3 +1,5 @@ +# -*- encoding: utf-8 -*- + from .base import Resource diff --git a/pygithub3/services/pull_requests/__init__.py b/pygithub3/services/pull_requests/__init__.py index 66d9e58..545f862 100644 --- a/pygithub3/services/pull_requests/__init__.py +++ b/pygithub3/services/pull_requests/__init__.py @@ -1,4 +1,5 @@ -from pygithub3.exceptions import BadRequest, NotFound +# -*- encoding: utf-8 -*- + from pygithub3.services.base import Service, MimeTypeMixin from .comments import Comments @@ -15,7 +16,10 @@ def list(self, user=None, repo=None): :param str user: Username :param str repo: Repository + :returns: A :doc:`result` + .. note:: + Remember :ref:`config precedence` """ return self._get_result( self.make_request('pull_requests.list', user=user, repo=repo) @@ -28,37 +32,43 @@ def get(self, number, user=None, repo=None): :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ return self._get( self.make_request('pull_requests.get', number=number, user=user, repo=repo) ) - def create(self, body, user=None, repo=None): + def create(self, data, user=None, repo=None): """Create a pull request - :param dict body: Data for the new pull request + :param dict data: Input. See `github pullrequests doc`_ :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ return self._post( - self.make_request('pull_requests.create', body=body, user=user, + self.make_request('pull_requests.create', body=data, user=user, repo=repo) ) - def update(self, number, body, user=None, repo=None): + def update(self, number, data, user=None, repo=None): """Update a pull request :param str number: The number of the the pull request to update - :param dict body: The data to update the pull request with + :param dict data: Input. See `github pullrequests doc`_ :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ return self._patch( self.make_request('pull_requests.update', number=number, - body=body, user=user, repo=repo) + body=data, user=user, repo=repo) ) def list_commits(self, number, user=None, repo=None): @@ -67,7 +77,10 @@ def list_commits(self, number, user=None, repo=None): :param str number: The number of the pull request to list commits for :param str user: Username :param str repo: Repository + :returns: A :doc:`result` + .. note:: + Remember :ref:`config precedence` """ return self._get_result( self.make_request('pull_requests.list_commits', number=number, @@ -80,23 +93,28 @@ def list_files(self, number, user=None, repo=None): :param str number: The number of the pull request to list files for :param str user: Username :param str repo: Repository + :returns: A :doc:`result` + .. note:: + Remember :ref:`config precedence` """ return self._get_result( self.make_request('pull_requests.list_files', number=number, user=user, repo=repo) ) - def merge_status(self, number, user=None, repo=None): + def is_merged(self, number, user=None, repo=None): """Gets whether a pull request has been merged or not. :param str number: The pull request to check :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ return self._bool( - self.make_request('pull_requests.merge_status', number=number, + self.make_request('pull_requests.is_merged', number=number, user=user, repo=repo) ) @@ -104,9 +122,13 @@ def merge(self, number, message='', user=None, repo=None): """Merge a pull request. :param str number: The pull request to merge + :param str message: Message of pull request :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` + This currently raises an HTTP 405 error if the request is not mergable. diff --git a/pygithub3/services/pull_requests/comments.py b/pygithub3/services/pull_requests/comments.py index 3aa6d0e..2edbfdf 100644 --- a/pygithub3/services/pull_requests/comments.py +++ b/pygithub3/services/pull_requests/comments.py @@ -1,11 +1,10 @@ +# -*- encoding: utf-8 -*- from pygithub3.services.base import Service, MimeTypeMixin class Comments(Service, MimeTypeMixin): """Consume `Review Comments API - `_ - - """ + `_ """ def list(self, number, user=None, repo=None): """List all the comments for a pull request @@ -13,7 +12,10 @@ def list(self, number, user=None, repo=None): :param str number: The number of the pull request :param str user: Username :param str repo: Repository + :returns: A :doc:`result` + .. note:: + Remember :ref:`config precedence` """ return self._get_result( self.make_request('pull_requests.comments.list', number=number, @@ -27,37 +29,44 @@ def get(self, number, user=None, repo=None): :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ return self._get( self.make_request('pull_requests.comments.get', number=number, user=user, repo=repo) ) - def create(self, number, body, user=None, repo=None): + def create(self, number, data, user=None, repo=None): """Create a comment :param str number: the pull request to comment on + :param dict data: Input. See `github pullrequests comments doc`_ :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ return self._post( self.make_request('pull_requests.comments.create', number=number, - body=body, user=user, repo=repo) + body=data, user=user, repo=repo) ) - def edit(self, number, body, user=None, repo=None): + def update(self, number, message, user=None, repo=None): """Edit a comment :param str number: The id of the comment to edit + :param str message: Comment message :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ - return self._patch( - self.make_request('pull_requests.comments.edit', number=number, - body=body, user=user, repo=repo) - ) + request = self.make_request('pull_requests.comments.edit', + number=number, body={'body': message}, user=user, repo=repo) + return self._patch(request) def delete(self, number, user=None, repo=None): """Delete a comment @@ -66,6 +75,8 @@ def delete(self, number, user=None, repo=None): :param str user: Username :param str repo: Repository + .. note:: + Remember :ref:`config precedence` """ return self._delete( self.make_request('pull_requests.comments.delete', number=number, diff --git a/pygithub3/tests/services/test_pull_requests.py b/pygithub3/tests/services/test_pull_requests.py index 7fc15b7..93646df 100644 --- a/pygithub3/tests/services/test_pull_requests.py +++ b/pygithub3/tests/services/test_pull_requests.py @@ -7,8 +7,7 @@ from pygithub3.tests.utils.core import TestCase from pygithub3.services.pull_requests import PullRequests, Comments -from pygithub3.resources.base import json -from pygithub3.requests.base import ValidationError +from pygithub3.requests.base import ValidationError, json from pygithub3.tests.utils.base import (mock_response, mock_response_result, mock_json) from pygithub3.tests.utils.services import _ @@ -107,19 +106,9 @@ def test_LIST_FILES(self, reqm): ('get', _('repos/user/repo/pulls/123/files')) ) - def test_MERGE_STATUS_true(self, reqm): - reqm.return_value = mock_response(204) - resp = self.service.merge_status(123) - self.assertEqual(True, resp) - self.assertEqual( - reqm.call_args[0], - ('head', _('repos/user/repo/pulls/123/merge')) - ) - - def test_MERGE_STATUS_false(self, reqm): - reqm.return_value = mock_response(404) - resp = self.service.merge_status(123) - self.assertEqual(False, resp) + def test_IS_MERGED(self, reqm): + resp = self.service.is_merged(123) + self.assertTrue(resp) self.assertEqual( reqm.call_args[0], ('head', _('repos/user/repo/pulls/123/merge')) From dd29354dd13d49c1d41bbc433df0f0c2e162c696 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 12 May 2012 23:16:56 +0200 Subject: [PATCH 31/78] Readme and authors updated --- AUTHORS.rst | 1 + README.rst | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 4415d5e..3497c25 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -23,3 +23,4 @@ Patches and Suggestions - Antti Kaihola - Francisco Marcos - Nathaniel Williams +- Alejandro Gómez diff --git a/README.rst b/README.rst index 6dfe6d4..d7fa447 100644 --- a/README.rst +++ b/README.rst @@ -42,13 +42,14 @@ Achievements - `Repos service `_ - `Gists service `_ - `Git Data service `_ +- `Pull requests service `_ TODO ----- -- Services: Issues, Orgs, Pull Requests, Events +- Services: Issues, Orgs, Events - Oauth authorization API (service?) -- Proxy methods into resources (e.g copitux.followers()) +- Proxy methods into resources (e.g copitux.followers) Contribute ----------- From 8c64210622d2d3aeabb3c23522e27dcebe2fe706 Mon Sep 17 00:00:00 2001 From: David Medina Date: Thu, 17 May 2012 22:59:33 +0200 Subject: [PATCH 32/78] refs #10 --- pygithub3/requests/pull_requests/comments.py | 2 +- pygithub3/services/pull_requests/comments.py | 6 +++--- pygithub3/tests/services/test_pull_requests.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pygithub3/requests/pull_requests/comments.py b/pygithub3/requests/pull_requests/comments.py index dd69894..1904f6c 100644 --- a/pygithub3/requests/pull_requests/comments.py +++ b/pygithub3/requests/pull_requests/comments.py @@ -32,7 +32,7 @@ def clean_body(self): return self.body -class Edit(Request): +class Update(Request): uri = 'repos/{user}/{repo}/pulls/comments/{number}' resource = Comment body_schema = { diff --git a/pygithub3/services/pull_requests/comments.py b/pygithub3/services/pull_requests/comments.py index 2edbfdf..ebc4e4b 100644 --- a/pygithub3/services/pull_requests/comments.py +++ b/pygithub3/services/pull_requests/comments.py @@ -54,9 +54,9 @@ def create(self, number, data, user=None, repo=None): ) def update(self, number, message, user=None, repo=None): - """Edit a comment + """Update a comment - :param str number: The id of the comment to edit + :param str number: The id of the comment to update :param str message: Comment message :param str user: Username :param str repo: Repository @@ -64,7 +64,7 @@ def update(self, number, message, user=None, repo=None): .. note:: Remember :ref:`config precedence` """ - request = self.make_request('pull_requests.comments.edit', + request = self.make_request('pull_requests.comments.update', number=number, body={'body': message}, user=user, repo=repo) return self._patch(request) diff --git a/pygithub3/tests/services/test_pull_requests.py b/pygithub3/tests/services/test_pull_requests.py index 93646df..4a9fd41 100644 --- a/pygithub3/tests/services/test_pull_requests.py +++ b/pygithub3/tests/services/test_pull_requests.py @@ -170,12 +170,12 @@ def test_CREATE_in_reply_to(self, reqm): ('post', _('repos/user/repo/pulls/1/comments')) ) - def test_EDIT(self, reqm): + def test_UPDATE(self, reqm): reqm.return_value = mock_response(200) data = { 'body': 'something completely different', } - self.service.edit(1, data) + self.service.update(1, data) self.assertEqual( reqm.call_args[0], ('patch', _('repos/user/repo/pulls/comments/1')) From 1a5d8d00c3ac9ccb230e1c9a368f1ca005076f75 Mon Sep 17 00:00:00 2001 From: David Schoonover Date: Thu, 17 May 2012 16:32:21 -0700 Subject: [PATCH 33/78] Require a current version of `requests` -- `pygithub3` installs, but later fails with all sorts of cryptic errors if you have an older, pre-existing version of `requests` installed when you install `pygithub3`. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index f229360..285540e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1 +1 @@ -requests +requests >= 0.12.1 From 61c438619634c80a9fb5579beb0a6609eaf00f2d Mon Sep 17 00:00:00 2001 From: Conor Branagan Date: Wed, 11 Apr 2012 17:11:08 -0400 Subject: [PATCH 34/78] Add issues service for issues, comments and events. has tests and updated docs --- docs/issues.rst | 47 ++++++++++ docs/services.rst | 1 + pygithub3/github.py | 5 ++ pygithub3/requests/issues/__init__.py | 51 +++++++++++ pygithub3/requests/issues/comments.py | 42 +++++++++ pygithub3/requests/issues/events.py | 22 +++++ pygithub3/resources/issues.py | 31 +++++++ pygithub3/services/issues/__init__.py | 82 +++++++++++++++++ pygithub3/services/issues/comments.py | 75 ++++++++++++++++ pygithub3/services/issues/events.py | 42 +++++++++ pygithub3/tests/services/test_issues.py | 115 ++++++++++++++++++++++++ 11 files changed, 513 insertions(+) create mode 100644 docs/issues.rst create mode 100644 pygithub3/requests/issues/__init__.py create mode 100644 pygithub3/requests/issues/comments.py create mode 100644 pygithub3/requests/issues/events.py create mode 100644 pygithub3/resources/issues.py create mode 100644 pygithub3/services/issues/__init__.py create mode 100644 pygithub3/services/issues/comments.py create mode 100644 pygithub3/services/issues/events.py create mode 100644 pygithub3/tests/services/test_issues.py diff --git a/docs/issues.rst b/docs/issues.rst new file mode 100644 index 0000000..26faf82 --- /dev/null +++ b/docs/issues.rst @@ -0,0 +1,47 @@ +.. _Issues service: + +Issues services +=============== + +**Fast sample**:: + + from pygithub3 import Github + + auth = dict(login='octocat', password='pass') + gh = Github(**auth) + + octocat_issues = gh.issues.list() + octocat_repo_issues = gh.issues.list_by_repo('octocat', 'Hello-World') + +Issues +----- + +.. autoclass:: pygithub3.services.issues.Issue + :members: + + .. attribute:: comments + + :ref:`Comments service` + + .. attribute:: events + + :ref:`Events service` + +.. _Comments service: + +Comments +---------- + +.. autoclass:: pygithub3.services.issues.Comments + :members: + +.. _ Events service: + +Events +------- + +.. autoclass:: pygithub3.services.issues.Comments + :members: + +.. _github issues doc: http://developer.github.com/v3/issues +.. _github comments doc: http://developer.github.com/v3/issues/comments \ No newline at end of file diff --git a/docs/services.rst b/docs/services.rst index 190ae99..2f268bb 100644 --- a/docs/services.rst +++ b/docs/services.rst @@ -74,5 +74,6 @@ List of services gists git_data pull_requests + issues .. _mimetypes: http://developer.github.com/v3/mime diff --git a/pygithub3/github.py b/pygithub3/github.py index 87c4a6d..5f95505 100644 --- a/pygithub3/github.py +++ b/pygithub3/github.py @@ -19,11 +19,16 @@ def __init__(self, **config): from pygithub3.services.gists import Gist from pygithub3.services.git_data import GitData from pygithub3.services.pull_requests import PullRequests + from pygithub3.services.issues import Issue self._users = User(**config) self._repos = Repo(**config) self._gists = Gist(**config) self._git_data = GitData(**config) self._pull_requests = PullRequests(**config) + self._users = User(**config) + self._repos = Repo(**config) + self._gists = Gist(**config) + self._issues = Issue(**config) @property def remaining_requests(self): diff --git a/pygithub3/requests/issues/__init__.py b/pygithub3/requests/issues/__init__.py new file mode 100644 index 0000000..ca8afb1 --- /dev/null +++ b/pygithub3/requests/issues/__init__.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.issues import Issue + +class List(Request): + + uri = 'issues' + resource = Issue + body_schema = { + 'schema': ('filter', 'state', 'labels', 'sort', 'direction', 'since'), + 'required': () + } + + +class List_by_repo(Request): + + uri = 'repos/{user}/{repo}/issues' + resource = Issue + body_schema = { + 'schema': ('milestone', 'state', 'assignee', 'mentioned', 'labels', + 'sort', 'direction', 'since'), + 'required': () + } + + +class Get(Request): + + uri = 'repos/{user}/{repo}/issues/{number}' + resource = Issue + + +class Create(Request): + + uri = 'repos/{user}/{repo}/issues' + resource = Issue + body_schema = { + 'schema': ('title', 'body', 'assignee', 'milestone', 'labels'), + 'required': ('title', ) + } + + +class Edit(Request): + + uri = 'repos/{user}/{repo}/issues/{number}' + resource = Issue + body_schema = { + 'schema': ('title', 'body', 'assignee', 'state', 'milestone', 'lables'), + 'required': () + } \ No newline at end of file diff --git a/pygithub3/requests/issues/comments.py b/pygithub3/requests/issues/comments.py new file mode 100644 index 0000000..0601db3 --- /dev/null +++ b/pygithub3/requests/issues/comments.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.issues import Comment + +class List(Request): + + uri = 'repos/{user}/{repo}/issues/{number}/comments' + resource = Comment + + +class Get(Request): + + uri = 'repos/{user}/{repo}/issues/comments/{id}' + resource = Comment + + +class Create(Request): + + uri = 'repos/{user}/{repo}/issues/{number}/comments' + resource = Comment + body_schema = { + 'schema': ('body', ), + 'required': ('body', ) + } + + +class Edit(Request): + + uri = 'repos/{user}/{repo}/issues/comments/{id}' + resource = Comment + body_schema = { + 'schema': ('body', ), + 'required': ('body', ) + } + + +class Delete(Request): + + uri = 'repos/{user}/{repo}/issues/comments/{id}' + resource = Comment \ No newline at end of file diff --git a/pygithub3/requests/issues/events.py b/pygithub3/requests/issues/events.py new file mode 100644 index 0000000..dfefe7e --- /dev/null +++ b/pygithub3/requests/issues/events.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.issues import Event + +class List_by_issue(Request): + + uri = 'repos/{user}/{repo}/issues/{number}/events' + resource = Event + + +class List_by_repo(Request): + + uri = 'repos/{user}/{repo}/issues/events' + resource = Event + + +class Get(Request): + + uri = 'repos/{user}/{repo}/issues/events/{id}' + resource = Event diff --git a/pygithub3/resources/issues.py b/pygithub3/resources/issues.py new file mode 100644 index 0000000..ce5b304 --- /dev/null +++ b/pygithub3/resources/issues.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from .base import Resource +from .users import User + +class Issue(Resource): + + _dates = ('created_at', 'updated_at') + _maps = {'assignee': User} + + def __str__(self): + return '' % getattr(self, 'number', '') + + +class Comment(Resource): + + _dates = ('created_at', 'update_at') + _maps = {'user': User} + + def __str__(self): + return '' % (getattr(self, 'user', '')) + + +class Event(Resource): + + _dates = ('created_at', ) + _maps = {'actor': User} + + def __str__(self): + return '' % (getattr(self, 'commit_id', '')) \ No newline at end of file diff --git a/pygithub3/services/issues/__init__.py b/pygithub3/services/issues/__init__.py new file mode 100644 index 0000000..0699f9e --- /dev/null +++ b/pygithub3/services/issues/__init__.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service +from .comments import Comments +from .events import Events + +class Issue(Service): + """ Consume `Issues API `_ """ + + def __init__(self, **config): + self.comments = Comments(**config) + self.events = Events(**config) + super(Issue, self).__init__(**config) + + def list(self, data={}): + """ List your issues + + :param dict data: Input. See `github issues doc`_ + :returns: A :doc:`result` + + .. warning:: + You must be authenticated + """ + request = self.request_builder('issues.list', body=data) + return self._get_result(request) + + def list_by_repo(self, user, repo, data={}): + """ List issues for a repo + + :param dict data: Input. See `github issues doc`_ + :returns: A :doc:`result` + """ + request = self.request_builder('issues.list_by_repo', user=user, + repo=repo, body=data) + return self._get_result(request) + + def get(self, user, repo, number): + """ Get a single issue + + :param str user: Username + :param str repo: Repo name + :param int number: Issue number + """ + request = self.request_builder('issues.get', user=user, repo=repo, + number=number) + return self._get(request) + + def create(self, user, repo, data): + """ Create an issue + + :param str user: Username + :param str repo: Repo name + :param dict data: Input. See `github issues doc`_ + + .. warning:: + You must be authenticated + + :: + + issues_service.create(dict(title='My test issue', + body='This needs to be fixed ASAP.', + assignee='copitux')) + """ + request = self.request_builder('issues.create', user=user, repo=repo, + body=data) + return self._post(request) + + def update(self, user, repo, number, data): + """ Edit an issue + + :param str user: Username + :param str repo: Repo name + :param int number: Issue number + :param dict data: Input. See `github issues doc`_ + + .. warning:: + You must be authenticated + """ + request = self.request_builder('issues.edit', user=user, repo=repo, + number=number, body=data) + return self._patch(request) \ No newline at end of file diff --git a/pygithub3/services/issues/comments.py b/pygithub3/services/issues/comments.py new file mode 100644 index 0000000..f367a58 --- /dev/null +++ b/pygithub3/services/issues/comments.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service + +class Comments(Service): + """ Consume `Comments API + `_ """ + + def list(self, user, repo, number): + """ List comments for an issue + + :param str user: Username + :param str repo: Repo name + :param int number: Issue number + :returns: A :doc:`result` + """ + request = self.request_builder('issues.comments.list', user=user, + repo=repo, number=number) + return self._get_result(request) + + def get(self, user, repo, id): + """ Get a single comment + + :param str user: Username + :param str repo: Repo name + :param int id: Comment id + """ + request = self.request_builder('issues.comments.get', user=user, + repo=repo, id=id) + return self._get(request) + + def create(self, user, repo, number, message): + """ Create a comment on an issue + + :param str user: Username + :param str repo: Repo name + :param int number: Issue number + :param str message: Comment message + + .. warning:: + You must be authenticated + """ + request = self.request_builder('issues.comments.create', user=user, + repo=repo, number=number, body={'body': message}) + return self._post(request) + + def update(self, user, repo, id, message): + """ Update a comment on an issue + + :param str user: Username + :param str repo: Repo name + :param int id: Issue id + :param str message: Comment message + + .. warning:: + You must be authenticated + """ + request = self.request_builder('issues.comments.edit', user=user, + repo=repo, id=id, body={'body': message}) + return self._patch(request) + + def delete(self, user, repo, id): + """ Delete a single comment + + :param str user: Username + :param str repo: Repo name + :param int id: Comment id + + ... warning:: + You must be authenticated + """ + request = self.request_builder('issues.comments.delete', user=user, + repo=repo, id=id) + self._delete(request) \ No newline at end of file diff --git a/pygithub3/services/issues/events.py b/pygithub3/services/issues/events.py new file mode 100644 index 0000000..0111db4 --- /dev/null +++ b/pygithub3/services/issues/events.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.services.base import Service + +class Events(Service): + """ Consume `Events API + `_ """ + + def list_by_issue(self, user, repo, number): + """ List events for an issue + + :param str user: Username + :param str repo: Repo name + :param int number: Issue number + :returns: A :doc:`result` + """ + request = self.request_builder('issues.events.list_by_issue', + user=user, repo=repo, number=number) + return self._get_result(request) + + def list_by_repo(self, user, repo): + """ List events for a repository + + :param str user: Username + :param str repo: Repo name + :returns: A :doc:`result` + """ + request = self.request_builder('issues.events.list_by_repo', + user=user, repo=repo) + return self._get_result(request) + + def get(self, user, repo, id): + """ Get a single event + + :param str user: Username + :param str repo: Repo name + :param int id: Comment id + """ + request = self.request_builder('issues.events.get', user=user, + repo=repo, id=id) + return self._get(request) \ No newline at end of file diff --git a/pygithub3/tests/services/test_issues.py b/pygithub3/tests/services/test_issues.py new file mode 100644 index 0000000..dfc56ca --- /dev/null +++ b/pygithub3/tests/services/test_issues.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import requests +from mock import patch, Mock + +from pygithub3.tests.utils.core import TestCase +from pygithub3.resources.base import json +from pygithub3.services.issues import Issue, Comments, Events +from pygithub3.tests.utils.base import (mock_response, mock_response_result, + mock_json) +from pygithub3.tests.utils.services import _ + +json.dumps = Mock(side_effect=mock_json) +json.loads = Mock(side_effect=mock_json) + + +@patch.object(requests.sessions.Session, 'request') +class TestIssuesService(TestCase): + + def setUp(self): + self.isu = Issue() + + def test_LIST_without_user(self, request_method): + request_method.return_value = mock_response_result() + self.isu.list().all() + self.assertEqual(request_method.call_args[0], ('get', _('issues'))) + + def test_LIST_by_repo(self, request_method): + request_method.return_value = mock_response_result() + self.isu.list_by_repo('octocat', 'Hello-World').all() + self.assertEqual(request_method.call_args[0], + ('get', _('repos/octocat/Hello-World/issues'))) + + def test_GET(self, request_method): + request_method.return_value = mock_response() + self.isu.get('octocat', 'Hello-World', 1) + self.assertEqual(request_method.call_args[0], + ('get', _('repos/octocat/Hello-World/issues/1'))) + + def test_CREATE(self, request_method): + request_method.return_value = mock_response('post') + self.isu.create('octocat', 'Hello-World', + dict(title='My issue', body='Fix this issue')) + self.assertEqual(request_method.call_args[0], + ('post', _('repos/octocat/Hello-World/issues'))) + + def test_UPDATE(self, request_method): + request_method.return_value = mock_response('patch') + self.isu.update('octocat', 'Hello-World', 1, + {'body': 'edited'}) + self.assertEqual(request_method.call_args[0], + ('patch', _('repos/octocat/Hello-World/issues/1'))) + + +@patch.object(requests.sessions.Session, 'request') +class TestCommentService(TestCase): + + def setUp(self): + self.cs = Comments() + + def test_LIST(self, request_method): + request_method.return_value = mock_response_result() + self.cs.list('octocat', 'Hello-World', 1).all() + self.assertEqual(request_method.call_args[0], + ('get', _('repos/octocat/Hello-World/issues/1/comments'))) + + def test_GET(self, request_method): + request_method.return_value = mock_response() + self.cs.get('octocat', 'Hello-World', 1) + self.assertEqual(request_method.call_args[0], + ('get', _('repos/octocat/Hello-World/issues/comments/1'))) + + def test_CREATE(self, request_method): + request_method.return_value = mock_response('post') + self.cs.create('octocat', 'Hello-World', 1, 'comment') + self.assertEqual(request_method.call_args[0], + ('post', _('repos/octocat/Hello-World/issues/1/comments'))) + + def test_UPDATE(self, request_method): + request_method.return_value = mock_response('patch') + self.cs.update('octocat', 'Hello-World', 1, 'new comment') + self.assertEqual(request_method.call_args[0], + ('patch', _('repos/octocat/Hello-World/issues/comments/1'))) + + def test_DELETE(self, request_method): + request_method.return_value = mock_response('delete') + self.cs.delete('octocat', 'Hello-World', 1) + self.assertEqual(request_method.call_args[0], + ('delete', _('repos/octocat/Hello-World/issues/comments/1'))) + + +@patch.object(requests.sessions.Session, 'request') +class TestEventsService(TestCase): + + def setUp(self): + self.ev = Events() + + def test_LIST_by_issue(self, request_method): + request_method.return_value = mock_response_result() + self.ev.list_by_issue('octocat', 'Hello-World', 1).all() + self.assertEqual(request_method.call_args[0], + ('get', _('repos/octocat/Hello-World/issues/1/events'))) + + def test_LIST_by_repo(self, request_method): + request_method.return_value = mock_response_result() + self.ev.list_by_repo('octocat', 'Hello-World').all() + self.assertEqual(request_method.call_args[0], + ('get', _('repos/octocat/Hello-World/issues/events'))) + + def test_GET(self, request_method): + request_method.return_value = mock_response() + self.ev.get('octocat', 'Hello-World', 1) + self.assertEqual(request_method.call_args[0], + ('get', _('repos/octocat/Hello-World/issues/events/1'))) \ No newline at end of file From 28b0668168e7662d0be8fbf3abedc15e597d178d Mon Sep 17 00:00:00 2001 From: Conor Branagan Date: Fri, 13 Apr 2012 16:31:16 -0400 Subject: [PATCH 35/78] Use github.issues for the issues service --- pygithub3/github.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pygithub3/github.py b/pygithub3/github.py index 5f95505..20ec258 100644 --- a/pygithub3/github.py +++ b/pygithub3/github.py @@ -70,3 +70,9 @@ def pull_requests(self): :ref:`Pull Requests service ` """ return self._pull_requests + + def issues(self): + """ + :ref:`Issues service ` + """ + return self._issues From 3310bde985aea5467ce93abcf9f0fc5008942f7c Mon Sep 17 00:00:00 2001 From: Conor Branagan Date: Fri, 13 Apr 2012 16:31:31 -0400 Subject: [PATCH 36/78] Fix some issues resources --- pygithub3/resources/issues.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygithub3/resources/issues.py b/pygithub3/resources/issues.py index ce5b304..e864e79 100644 --- a/pygithub3/resources/issues.py +++ b/pygithub3/resources/issues.py @@ -7,7 +7,7 @@ class Issue(Resource): _dates = ('created_at', 'updated_at') - _maps = {'assignee': User} + _maps = {'assignee': User, 'user': User} def __str__(self): return '' % getattr(self, 'number', '') @@ -15,7 +15,7 @@ def __str__(self): class Comment(Resource): - _dates = ('created_at', 'update_at') + _dates = ('created_at', 'updated_at') _maps = {'user': User} def __str__(self): @@ -25,7 +25,7 @@ def __str__(self): class Event(Resource): _dates = ('created_at', ) - _maps = {'actor': User} + _maps = {'actor': User, 'issue': Issue} def __str__(self): return '' % (getattr(self, 'commit_id', '')) \ No newline at end of file From 8972834a85a17ebbeb326a9d4493725d53913e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Thu, 26 Apr 2012 21:00:13 +0200 Subject: [PATCH 37/78] Labels and Milestones services added --- pygithub3/requests/issues/comments.py | 2 +- pygithub3/requests/issues/labels.py | 81 ++++++++++++ pygithub3/requests/issues/milestones.py | 38 ++++++ pygithub3/requests/repos/__init__.py | 16 +++ pygithub3/resources/issues.py | 26 +++- pygithub3/services/issues/__init__.py | 7 +- pygithub3/services/issues/comments.py | 2 +- pygithub3/services/issues/events.py | 2 +- pygithub3/services/issues/labels.py | 159 ++++++++++++++++++++++++ pygithub3/services/issues/milestones.py | 104 ++++++++++++++++ pygithub3/services/repos/__init__.py | 30 +++++ pygithub3/tests/services/test_issues.py | 107 +++++++++++++++- pygithub3/tests/services/test_repos.py | 12 ++ 13 files changed, 578 insertions(+), 8 deletions(-) create mode 100644 pygithub3/requests/issues/labels.py create mode 100644 pygithub3/requests/issues/milestones.py create mode 100644 pygithub3/services/issues/labels.py create mode 100644 pygithub3/services/issues/milestones.py diff --git a/pygithub3/requests/issues/comments.py b/pygithub3/requests/issues/comments.py index 0601db3..638c9cf 100644 --- a/pygithub3/requests/issues/comments.py +++ b/pygithub3/requests/issues/comments.py @@ -39,4 +39,4 @@ class Edit(Request): class Delete(Request): uri = 'repos/{user}/{repo}/issues/comments/{id}' - resource = Comment \ No newline at end of file + resource = Comment diff --git a/pygithub3/requests/issues/labels.py b/pygithub3/requests/issues/labels.py new file mode 100644 index 0000000..47337f9 --- /dev/null +++ b/pygithub3/requests/issues/labels.py @@ -0,0 +1,81 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.issues import Label + + + +class Get(Request): + uri = 'repos/{user}/{repo}/labels/{name}' + resource = Label + + +class Create(Request): + uri = 'repos/{user}/{repo}/labels' + resource = Label + body_schema = { + 'schema': ('name', 'color'), + 'required': ('name', 'color' ) + } + + def validate_color(color): + color = color.get('color', '') + if not Label.is_valid_color(color): + raise ValidationError('colors must have 6 hexadecimal characters, ' + 'without # in the beggining') + + +class Update(Request): + + uri = 'repos/{user}/{repo}/labels/{name}' + resource = Label + body_schema = { + 'schema': ('name', 'color'), + 'required': ('name', 'color' ) + } + + def validate_color(color): + color = color.get('color', '') + if not Label.is_valid_color(color): + raise ValidationError('colors must have 6 hexadecimal characters, ' + 'without # in the beggining') + + +class Delete(Request): + uri = 'repos/{user}/{repo}/labels/{name}' + resource = Label + + +class List_by_repo(Request): + uri = 'repos/{user}/{repo}/labels' + resource = Label + + + +class List_by_issue(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels' + resource = Label + + +class Add_to_issue(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels' + resource = Label + +class Remove_from_issue(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels/{name}' + resource = Label + + +class Replace_all(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels' + resource = Label + + +class Remove_all(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels' + resource = Label + + +class List_by_milestone(Request): + uri = 'repos/{user}/{repo}/milestones/{number}/labels' + resource = Label diff --git a/pygithub3/requests/issues/milestones.py b/pygithub3/requests/issues/milestones.py new file mode 100644 index 0000000..355c3b0 --- /dev/null +++ b/pygithub3/requests/issues/milestones.py @@ -0,0 +1,38 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.issues import Milestone + + +class List(Request): + uri = 'repos/{user}/{repo}/milestones' + resource = Milestone + + +class Get(Request): + uri = 'repos/{user}/{repo}/milestones/{number}' + resource = Milestone + + +class Create(Request): + uri = 'repos/{user}/{repo}/milestones' + resource = Milestone + body_schema = { + 'schema': ('title', 'state', 'description', 'due_on'), + 'required': ('title',) + } + + +class Update(Request): + + uri = 'repos/{user}/{repo}/milestones/{number}' + resource = Milestone + body_schema = { + 'schema': ('title', 'state', 'description', 'due_on'), + 'required': ('title',) + } + + +class Delete(Request): + uri = 'repos/{user}/{repo}/milestones/{number}' + resource = Milestone diff --git a/pygithub3/requests/repos/__init__.py b/pygithub3/requests/repos/__init__.py index cd920fe..7a145f2 100644 --- a/pygithub3/requests/repos/__init__.py +++ b/pygithub3/requests/repos/__init__.py @@ -2,6 +2,7 @@ from pygithub3.requests.base import Request, ValidationError from pygithub3.resources.users import User +from pygithub3.resources.issues import Label, Milestone from pygithub3.resources.repos import Repo, Team, Tag, Branch class List(Request): @@ -78,3 +79,18 @@ class List_branches(Request): uri = 'repos/{user}/{repo}/branches' resource = Branch + +class List_labels(Request): + + uri = 'repos/{user}/{repo}/labels' + resource = Label + +class List_milestones(Request): + + uri = 'repos/{user}/{repo}/milestones' + resource = Milestone + # TODO: validate + body_schema = { + 'schema': ('state', 'sort', 'direction'), + 'required': () + } diff --git a/pygithub3/resources/issues.py b/pygithub3/resources/issues.py index e864e79..c560faa 100644 --- a/pygithub3/resources/issues.py +++ b/pygithub3/resources/issues.py @@ -1,9 +1,33 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- +import re + from .base import Resource from .users import User + +class Label(Resource): + @staticmethod + def is_valid_color(color): + valid_color = re.compile(r'[0-9abcdefABCDEF]{6}') + match = valid_color(color) + if match is None: + return False + return match.start() == 0 and match.end() == len(color) + + def __str__(self): + return '