aboutsummaryrefslogtreecommitdiffstats
path: root/pygithub3
diff options
context:
space:
mode:
Diffstat (limited to 'pygithub3')
-rw-r--r--pygithub3/core/client.py2
-rw-r--r--pygithub3/exceptions.py2
-rw-r--r--pygithub3/github.py9
-rw-r--r--pygithub3/requests/pull_requests/__init__.py62
-rw-r--r--pygithub3/requests/pull_requests/comments.py43
-rw-r--r--pygithub3/resources/pull_requests.py20
-rw-r--r--pygithub3/services/pull_requests/__init__.py129
-rw-r--r--pygithub3/services/pull_requests/comments.py73
-rw-r--r--pygithub3/tests/services/test_pull_requests.py201
-rw-r--r--pygithub3/tests/utils/base.py2
10 files changed, 540 insertions, 3 deletions
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 @@ class Client(object):
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 @@ class Github(object):
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 @@ class Github(object):
:ref:`Gists service <Gists service>`
"""
return self._gists
+
+ @property
+ def pull_requests(self):
+ """
+ :ref:`Pull Requests service <Pull Requests service`
+ """
+ return self._pull_requests
diff --git a/pygithub3/requests/pull_requests/__init__.py b/pygithub3/requests/pull_requests/__init__.py
new file mode 100644
index 0000000..7e85f8f
--- /dev/null
+++ b/pygithub3/requests/pull_requests/__init__.py
@@ -0,0 +1,62 @@
+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
+
+
+class List(Request):
+ uri = 'repos/{user}/{repo}/pulls'
+ resource = PullRequest
+
+
+class Get(Request):
+ uri = 'repos/{user}/{repo}/pulls/{number}'
+ resource = PullRequest
+
+
+class Create(Request):
+ uri = 'repos/{user}/{repo}/pulls'
+ resource = PullRequest
+ body_schema = {
+ 'schema': ('title', 'body', 'base', 'head', 'issue'),
+ 'required': ('base', 'head'),
+ }
+
+ def validate_body(self, parsed):
+ if (not ('title' in parsed and 'body' in parsed) and
+ not 'issue' in parsed):
+ raise ValidationError('pull request creation requires either an '
+ 'issue number or a title and body')
+
+class Update(Request):
+ uri = 'repos/{user}/{repo}/pulls/{number}'
+ resource = PullRequest
+ body_schema = {
+ 'schema': ('title', 'body', 'state'),
+ 'required': (),
+ }
+
+ def validate_body(self, body):
+ if 'state' in body and body['state'] not in ['open', 'closed']:
+ raise ValidationError('If a state is specified, it must be one '
+ 'of "open" or "closed"')
+
+
+class List_commits(Request):
+ uri = 'repos/{user}/{repo}/pulls/{number}/commits'
+ resource = Commit
+
+
+class List_files(Request):
+ uri = 'repos/{user}/{repo}/pulls/{number}/files'
+ resource = File
+
+
+class Merge_status(Request):
+ uri = 'repos/{user}/{repo}/pulls/{number}/merge'
+ resource = Raw
+
+
+class Merge(Request):
+ uri = 'repos/{user}/{repo}/pulls/{number}/merge'
+ resource = Raw
diff --git a/pygithub3/requests/pull_requests/comments.py b/pygithub3/requests/pull_requests/comments.py
new file mode 100644
index 0000000..fa1e5b6
--- /dev/null
+++ b/pygithub3/requests/pull_requests/comments.py
@@ -0,0 +1,43 @@
+from pygithub3.requests.base import Request
+from pygithub3.resources.pull_requests import Comment
+
+
+class List(Request):
+ uri = 'repos/{user}/{repo}/pulls/{number}/comments'
+ resource = Comment
+
+
+class Get(Request):
+ uri = 'repos/{user}/{repo}/pulls/comments/{number}'
+ resource = Comment
+
+
+class Create(Request):
+ uri = 'repos/{user}/{repo}/pulls/{number}/comments'
+ resource = Comment
+ body_schema = {
+ 'schema': ('body', 'commit_id', 'path', 'position', 'in_reply_to'),
+ '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):
+ raise ValidationError('supply either in_reply_to or commit_id, '
+ 'path, and position')
+
+
+class Edit(Request):
+ uri = 'repos/{user}/{repo}/pulls/comments/{number}'
+ resource = Comment
+ body_schema = {
+ 'schema': ('body',),
+ 'required': ('body',),
+ }
+
+
+class Delete(Request):
+ uri = 'repos/{user}/{repo}/pulls/comments/{number}'
+ resource = Comment
diff --git a/pygithub3/resources/pull_requests.py b/pygithub3/resources/pull_requests.py
new file mode 100644
index 0000000..1fe2623
--- /dev/null
+++ b/pygithub3/resources/pull_requests.py
@@ -0,0 +1,20 @@
+from .base import Resource
+
+
+class PullRequest(Resource):
+ _dates = ('created_at', 'updated_at', 'closed_at', 'merged_at')
+
+ def __str__(self):
+ return '<PullRequest (%s)>' % getattr(self, 'title', '')
+
+
+class File(Resource):
+ def __str__(self):
+ return '<File (%s)>' % getattr(self, 'filename', '')
+
+
+class Comment(Resource):
+ _dates = ('created_at', 'updated_at')
+
+ def __str__(self):
+ return '<Comment (#%s)>' % 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 <http://developer.github.com/v3/pulls/>`_"""
+
+ 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
+ <http://developer.github.com/v3/pulls/comments/>`_
+
+ """
+
+ 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