aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Medina <davidmedina9@gmail.com>2012-06-16 13:56:29 +0200
committerDavid Medina <davidmedina9@gmail.com>2012-06-16 13:58:12 +0200
commit107b12b3b48040488ff6ddd54cb1300200dc8b37 (patch)
treef35c57bb8b5c5cd85511aaad1c0df0e4f97c0c25
parentMerge #5 'services/orgs' (diff)
parentTests on services.issues working (diff)
downloadpython-github3-107b12b3b48040488ff6ddd54cb1300200dc8b37.tar.xz
python-github3-107b12b3b48040488ff6ddd54cb1300200dc8b37.zip
Merge #12 'services/issues'
-rw-r--r--.gitignore1
-rw-r--r--docs/issues.rst74
-rw-r--r--docs/services.rst1
-rw-r--r--pygithub3/github.py10
-rw-r--r--pygithub3/requests/base.py4
-rw-r--r--pygithub3/requests/issues/__init__.py44
-rw-r--r--pygithub3/requests/issues/comments.py42
-rw-r--r--pygithub3/requests/issues/events.py22
-rw-r--r--pygithub3/requests/issues/labels.py77
-rw-r--r--pygithub3/requests/issues/milestones.py39
-rw-r--r--pygithub3/requests/repos/__init__.py1
-rw-r--r--pygithub3/resources/base.py7
-rw-r--r--pygithub3/resources/issues.py65
-rw-r--r--pygithub3/services/base.py14
-rw-r--r--pygithub3/services/issues/__init__.py121
-rw-r--r--pygithub3/services/issues/comments.py90
-rw-r--r--pygithub3/services/issues/events.py42
-rw-r--r--pygithub3/services/issues/labels.py197
-rw-r--r--pygithub3/services/issues/milestones.py96
-rw-r--r--pygithub3/tests/resources/test_issues.py17
-rw-r--r--pygithub3/tests/services/test_core.py13
-rw-r--r--pygithub3/tests/services/test_issues.py233
22 files changed, 1204 insertions, 6 deletions
diff --git a/.gitignore b/.gitignore
index 269c735..f0c73ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@
MANIFEST
docs/_build
dist/
+build/
*egg-info
diff --git a/docs/issues.rst b/docs/issues.rst
new file mode 100644
index 0000000..708d363
--- /dev/null
+++ b/docs/issues.rst
@@ -0,0 +1,74 @@
+.. _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`
+
+ .. attribute:: labels
+
+ :ref:`Labels service`
+
+ .. attribute:: milestones
+
+ :ref:`Milestones service`
+
+.. _Comments service:
+
+Comments
+--------
+
+.. autoclass:: pygithub3.services.issues.Comments
+ :members:
+
+.. _Events service:
+
+Events
+------
+
+.. autoclass:: pygithub3.services.issues.Events
+ :members:
+
+.. _Labels service:
+
+Labels
+------
+
+.. autoclass:: pygithub3.services.issues.Labels
+ :members:
+
+.. _Milestones service:
+
+Milestones
+----------
+
+.. autoclass:: pygithub3.services.issues.Milestones
+ :members:
+
+.. _github issues doc: http://developer.github.com/v3/issues
+.. _github comments doc: http://developer.github.com/v3/issues/comments
+.. _github events doc: http://developer.github.com/v3/issues/events
+.. _github labels doc: http://developer.github.com/v3/issues/labels
+.. _github milestones doc: http://developer.github.com/v3/issues/milestones
diff --git a/docs/services.rst b/docs/services.rst
index f7de042..7152aca 100644
--- a/docs/services.rst
+++ b/docs/services.rst
@@ -75,5 +75,6 @@ List of services
git_data
pull_requests
orgs
+ issues
.. _mimetypes: http://developer.github.com/v3/mime
diff --git a/pygithub3/github.py b/pygithub3/github.py
index d135865..50b5a1a 100644
--- a/pygithub3/github.py
+++ b/pygithub3/github.py
@@ -20,12 +20,14 @@ class Github(object):
from pygithub3.services.git_data import GitData
from pygithub3.services.pull_requests import PullRequests
from pygithub3.services.orgs import Org
+ 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._orgs = Org(**config)
+ self._issues = Issue(**config)
@property
def remaining_requests(self):
@@ -68,8 +70,16 @@ class Github(object):
"""
return self._pull_requests
+ @property
def orgs(self):
"""
:ref:`Orgs service <Orgs service>`
"""
return self._orgs
+
+ @property
+ def issues(self):
+ """
+ :ref:`Issues service <Issues service>`
+ """
+ return self._issues
diff --git a/pygithub3/requests/base.py b/pygithub3/requests/base.py
index c4fe5cc..6088bae 100644
--- a/pygithub3/requests/base.py
+++ b/pygithub3/requests/base.py
@@ -27,8 +27,8 @@ class Body(object):
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__)
+ raise ValidationError("It needs a content dictionary (%s)" % (
+ self.content, ))
parsed = dict([(key, self.content[key]) for key in self.schema
if key in self.content])
for attr_required in self.required:
diff --git a/pygithub3/requests/issues/__init__.py b/pygithub3/requests/issues/__init__.py
new file mode 100644
index 0000000..d6c7b06
--- /dev/null
+++ b/pygithub3/requests/issues/__init__.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from pygithub3.requests.base import Request
+from pygithub3.resources.issues import Issue
+
+
+class List(Request):
+
+ uri = 'issues'
+ resource = Issue
+
+
+class List_by_repo(Request):
+
+ uri = 'repos/{user}/{repo}/issues'
+ resource = Issue
+
+
+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 Update(Request):
+
+ uri = 'repos/{user}/{repo}/issues/{number}'
+ resource = Issue
+ body_schema = {
+ 'schema': ('title', 'body', 'assignee', 'state', 'milestone',
+ 'lables'),
+ 'required': ()
+ }
diff --git a/pygithub3/requests/issues/comments.py b/pygithub3/requests/issues/comments.py
new file mode 100644
index 0000000..638c9cf
--- /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
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/requests/issues/labels.py b/pygithub3/requests/issues/labels.py
new file mode 100644
index 0000000..9ae6025
--- /dev/null
+++ b/pygithub3/requests/issues/labels.py
@@ -0,0 +1,77 @@
+# -*- encoding: utf-8 -*-
+
+from pygithub3.requests.base import Request, ValidationError
+from pygithub3.resources.issues import Label
+
+
+class List(Request):
+
+ uri = 'repos/{user}/{repo}/labels'
+ resource = 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 clean_body(self):
+ color = self.body.get('color', '')
+ if not Label.is_valid_color(color):
+ raise ValidationError('colors must have 6 hexadecimal characters, '
+ 'without # in the beggining')
+ else:
+ return self.body
+
+
+class Update(Create):
+
+ uri = 'repos/{user}/{repo}/labels/{name}'
+ resource = Label
+ body_schema = {
+ 'schema': ('name', 'color'),
+ 'required': ('name', 'color' )
+ }
+
+
+class Delete(Request):
+ uri = 'repos/{user}/{repo}/labels/{name}'
+ 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..4093c7e
--- /dev/null
+++ b/pygithub3/requests/issues/milestones.py
@@ -0,0 +1,39 @@
+# -*- encoding: utf-8 -*-
+
+from pygithub3.requests.base import Request, ValidationError
+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',)
+ }
+
+ def clean_body(self): # Test if API normalize it
+ state = self.body.get('state', '')
+ if state and state.lower() not in ('open', 'closed'):
+ raise ValidationError("'state' must be 'open' or 'closed'")
+ return self.body
+
+
+class Update(Create):
+
+ uri = 'repos/{user}/{repo}/milestones/{number}'
+
+
+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 7bbcf3e..a1b3607 100644
--- a/pygithub3/requests/repos/__init__.py
+++ b/pygithub3/requests/repos/__init__.py
@@ -4,6 +4,7 @@ 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.issues import Label, Milestone
class List(Request):
diff --git a/pygithub3/resources/base.py b/pygithub3/resources/base.py
index 7045529..4c65b8b 100644
--- a/pygithub3/resources/base.py
+++ b/pygithub3/resources/base.py
@@ -1,8 +1,12 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
+from datetime import datetime
+
from pygithub3.core.utils import json
+GITHUB_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
+
class Resource(object):
@@ -44,9 +48,8 @@ class Resource(object):
return wrapper
def parse_date(string_date):
- from datetime import datetime
try:
- date = datetime.strptime(string_date, '%Y-%m-%dT%H:%M:%SZ')
+ date = datetime.strptime(string_date, GITHUB_DATE_FORMAT)
except TypeError:
date = None
return date
diff --git a/pygithub3/resources/issues.py b/pygithub3/resources/issues.py
new file mode 100644
index 0000000..b2301a6
--- /dev/null
+++ b/pygithub3/resources/issues.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+import re
+
+from .base import Resource
+from .users import User
+from .pull_requests import PullRequest
+
+
+class Label(Resource):
+
+ @staticmethod
+ def is_valid_color(color):
+ valid_color = re.compile(r'[0-9abcdefABCDEF]{6}')
+ match = valid_color.match(color)
+ if match is None:
+ return False
+ return match.start() == 0 and match.end() == len(color)
+
+ def __str__(self):
+ return '<Label (%s)>' % getattr(self, 'name', '')
+
+
+class Milestone(Resource):
+
+ _dates = ('created_at', 'due_on')
+ _maps = {'creator': User}
+
+ def __str__(self):
+ return '<Milestone (%s)>' % getattr(self, 'title', '')
+
+
+class Issue(Resource):
+
+ _dates = ('created_at', 'updated_at', 'closed_at')
+ _maps = {
+ 'assignee': User,
+ 'user': User,
+ 'milestone': Milestone,
+ 'pull_request': PullRequest
+ }
+
+ _collection_maps = {'labels': Label}
+
+ def __str__(self):
+ return '<Issue (%s)>' % getattr(self, 'number', '')
+
+
+class Comment(Resource):
+
+ _dates = ('created_at', 'updated_at')
+ _maps = {'user': User}
+
+ def __str__(self):
+ return '<Comment (%s)>' % (getattr(self, 'user', ''))
+
+
+class Event(Resource):
+
+ _dates = ('created_at', )
+ _maps = {'actor': User, 'issue': Issue}
+
+ def __str__(self):
+ return '<Event (%s)>' % (getattr(self, 'commit_id', ''))
diff --git a/pygithub3/services/base.py b/pygithub3/services/base.py
index 649b2b3..5fed1db 100644
--- a/pygithub3/services/base.py
+++ b/pygithub3/services/base.py
@@ -1,10 +1,13 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
+from datetime import datetime
+
from pygithub3.core.client import Client
+from pygithub3.core.errors import NotFound
from pygithub3.core.result import smart, normal
from pygithub3.requests.base import Factory
-from pygithub3.core.errors import NotFound
+from pygithub3.resources.base import GITHUB_DATE_FORMAT
class Service(object):
@@ -41,6 +44,15 @@ class Service(object):
self._client = Client(**config)
self.request_builder = Factory()
+ def _normalize_date(self, key, _dict):
+ """ If ``key`` comes as ``datetime``, it'll normalize it """
+ try:
+ key = str(key)
+ date = datetime.strftime(_dict.get(key), GITHUB_DATE_FORMAT)
+ _dict.update({key: date})
+ except:
+ pass
+
@property
def remaining_requests(self):
return Client.remaining_requests
diff --git a/pygithub3/services/issues/__init__.py b/pygithub3/services/issues/__init__.py
new file mode 100644
index 0000000..7851e6f
--- /dev/null
+++ b/pygithub3/services/issues/__init__.py
@@ -0,0 +1,121 @@
+# -*- encoding: utf-8 -*-
+
+from pygithub3.services.base import Service, MimeTypeMixin
+from .comments import Comments
+from .events import Events
+from .labels import Labels
+from .milestones import Milestones
+
+
+class Issue(Service, MimeTypeMixin):
+ """ Consume `Issues API <http://developer.github.com/v3/issues>`_ """
+
+ def __init__(self, **config):
+ self.comments = Comments(**config)
+ self.events = Events(**config)
+ self.labels = Labels(**config)
+ self.milestones = Milestones(**config)
+ super(Issue, self).__init__(**config)
+
+ def list(self, filter='assigned', state='open', labels='', sort='created',
+ direction='desc', since=None):
+ """ List your issues
+
+ :param str filter: 'assigned', 'created', 'mentioned' or 'subscribed'
+ :param str state: 'open' or 'closed'
+ :param str labels: List of comma separated Label names. e.g: bug,ui,
+ @high
+ :param str sort: 'created', 'updated' or 'comments'
+ :param str direction: 'asc' or 'desc'
+ :param datetime since: Date filter (datetime or str in ISO 8601)
+ :returns: A :doc:`result`
+
+ .. warning::
+ You must be authenticated
+ """
+ params = dict(filter=filter, state=state, labels=labels, sort=sort,
+ direction=direction)
+ self._normalize_date('since', params)
+ request = self.request_builder('issues.list')
+ return self._get_result(request, **params)
+
+ def list_by_repo(self, user=None, repo=None, milestone='*', state='open',
+ assignee='*', mentioned='', labels='', sort='created',
+ direction='desc', since=None):
+ """ List issues for a repo
+
+ :param str milestone: Milestone ID, 'none' or '*'
+ :param str state: 'open' or 'closed'
+ :param str assignee: Username, 'none' or '*'
+ :param str mentioned: Username
+ :param str labels: List of comma separated Label names. e.g: bug,ui,
+ @high
+ :param str sort: 'created', 'updated' or 'comments'
+ :param str direction: 'asc' or 'desc'
+ :param datetime since: Date filter (datetime or str in ISO 8601)
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ params = dict(milestone=milestone, state=state, assignee=assignee,
+ mentioned=mentioned, labels=labels, sort=sort, direction=direction)
+ self._normalize_date('since', params)
+ request = self.make_request('issues.list_by_repo', user=user,
+ repo=repo)
+ return self._get_result(request, **params)
+
+ def get(self, number, user=None, repo=None):
+ """ Get a single issue
+
+ :param int number: Issue number
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.get', user=user, repo=repo,
+ number=number)
+ return self._get(request)
+
+ def create(self, data, user=None, repo=None):
+ """ Create an issue
+
+ :param dict data: Input. See `github issues doc`_
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+
+ ::
+
+ issues_service.create(dict(title='My test issue',
+ body='This needs to be fixed ASAP.',
+ assignee='copitux'))
+ """
+ request = self.make_request('issues.create', user=user, repo=repo,
+ body=data)
+ return self._post(request)
+
+ def update(self, number, data, user=None, repo=None):
+ """ Update an issue
+
+ :param int number: Issue number
+ :param dict data: Input. See `github issues doc`_
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.update', user=user, repo=repo,
+ number=number, body=data)
+ return self._patch(request)
diff --git a/pygithub3/services/issues/comments.py b/pygithub3/services/issues/comments.py
new file mode 100644
index 0000000..54d4287
--- /dev/null
+++ b/pygithub3/services/issues/comments.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from pygithub3.services.base import Service, MimeTypeMixin
+
+class Comments(Service, MimeTypeMixin):
+ """ Consume `Comments API
+ <http://developer.github.com/v3/issues/comments>`_ """
+
+ def list(self, number, user=None, repo=None):
+ """ List comments for an issue
+
+ :param int number: Issue number
+ :param str user: Username
+ :param str repo: Repo name
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.comments.list', user=user,
+ repo=repo, number=number)
+ return self._get_result(request)
+
+ def get(self, id, user=None, repo=None):
+ """ Get a single comment
+
+ :param int id: Comment id
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.comments.get', user=user,
+ repo=repo, id=id)
+ return self._get(request)
+
+ def create(self, number, message, user=None, repo=None):
+ """ Create a comment on an issue
+
+ :param int number: Issue number
+ :param str message: Comment message
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.comments.create', user=user,
+ repo=repo, number=number, body={'body': message})
+ return self._post(request)
+
+ def update(self, id, message, user=None, repo=None):
+ """ Update a comment on an issue
+
+ :param int id: Issue id
+ :param str message: Comment message
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.comments.edit', user=user,
+ repo=repo, id=id, body={'body': message})
+ return self._patch(request)
+
+ def delete(self, id, user=None, repo=None):
+ """ Delete a single comment
+
+ :param int id: Comment id
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.comments.delete', user=user,
+ repo=repo, id=id)
+ self._delete(request)
diff --git a/pygithub3/services/issues/events.py b/pygithub3/services/issues/events.py
new file mode 100644
index 0000000..92bb332
--- /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
+ <http://developer.github.com/v3/issues/events>`_ """
+
+ def list_by_issue(self, number, user=None, repo=None):
+ """ List events for an issue
+
+ :param int number: Issue number
+ :param str user: Username
+ :param str repo: Repo name
+ :returns: A :doc:`result`
+ """
+ request = self.make_request('issues.events.list_by_issue',
+ user=user, repo=repo, number=number)
+ return self._get_result(request)
+
+ def list_by_repo(self, user=None, repo=None):
+ """ List events for a repository
+
+ :param str user: Username
+ :param str repo: Repo name
+ :returns: A :doc:`result`
+ """
+ request = self.make_request('issues.events.list_by_repo',
+ user=user, repo=repo)
+ return self._get_result(request)
+
+ def get(self, id, user=None, repo=None):
+ """ Get a single event
+
+ :param int id: Comment id
+ :param str user: Username
+ :param str repo: Repo name
+ """
+ request = self.make_request('issues.events.get', user=user,
+ repo=repo, id=id)
+ return self._get(request)
diff --git a/pygithub3/services/issues/labels.py b/pygithub3/services/issues/labels.py
new file mode 100644
index 0000000..d1119d3
--- /dev/null
+++ b/pygithub3/services/issues/labels.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from pygithub3.services.base import Service
+
+
+class Labels(Service):
+ """ Consume `Labels API
+ <http://developer.github.com/v3/issues/labels>`_ """
+
+ def list(self, user=None, repo=None):
+ """ Get repository's labels
+
+ :param str user: Username
+ :param str repo: Repository
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.list', user=user, repo=repo)
+ return self._get_result(request)
+
+ def get(self, name, user=None, repo=None):
+ """ Get a single label
+
+ :param str name: Label name
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.get', user=user,
+ repo=repo, name=name)
+ return self._get(request)
+
+ def create(self, data, user=None, repo=None):
+ """ Create a label on an repo
+
+ :param dict data: Input. See `github labels doc`_
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.create', user=user,
+ repo=repo, body=data)
+ return self._post(request)
+
+ def update(self, name, data, user=None, repo=None):
+ """ Update a label on an repo
+
+ :param str name: Label name
+ :param dict data: Input. See `github labels doc`_
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.update', user=user,
+ repo=repo, name=name, body=data)
+ return self._patch(request)
+
+ def delete(self, name, user=None, repo=None):
+ """ Delete a label on an repo
+
+ :param str name: Label name
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.delete', user=user,
+ repo=repo, name=name)
+ return self._delete(request)
+
+ def list_by_issue(self, number, user=None, repo=None):
+ """ List labels for an issue
+
+ :param int number: Issue number
+ :param str user: Username
+ :param str repo: Repo name
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.list_by_issue', user=user,
+ repo=repo, number=number)
+ return self._get(request)
+
+ def add_to_issue(self, number, labels, user=None, repo=None):
+ """ Add labels to issue
+
+ :param int number: Issue number
+ :param str user: Username
+ :param str repo: Repo name
+ :param list labels: Label names
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+
+ ::
+
+ labels_service.add_to_issue(2, user='github', repo='github',
+ 'label1', 'label2', 'label3')
+ """
+ request = self.make_request('issues.labels.add_to_issue', user=user,
+ repo=repo, number=number, body=map(str, labels))
+ return self._post(request)
+
+ def remove_from_issue(self, number, label, user=None, repo=None):
+ """ Remove a label from an issue
+
+ :param int number: Issue number
+ :param str label: Label name
+ :param str user: Username
+ :param str repo: Repo name
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.remove_from_issue',
+ user=user,
+ repo=repo,
+ number=number,
+ name=label)
+ return self._delete(request)
+
+ def replace_all(self, number, labels, user=None, repo=None):
+ """ Replace all labels for a issue
+
+ :param int number: Issue number
+ :param list labels: New labels
+ :param str user: Username
+ :param str repo: Repo name
+ :returns: A :doc:`result`
+
+ .. note::
+ If labels weren't especified, it'd remove all labels from the issue
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.replace_all',
+ user=user,
+ repo=repo,
+ number=number,
+ body=map(str, labels))
+ return self._put(request)
+
+ def remove_all(self, number, user=None, repo=None):
+ """ Remove all labels from a issue
+
+ :param int number: Issue number
+ :param str user: Username
+ :param str repo: Repo name
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.remove_all',
+ user=user,
+ repo=repo,
+ number=number,)
+ return self._delete(request)
+
+ def list_by_milestone(self, number, user=None, repo=None):
+ """ Get labels for every issue in a milestone
+
+ :param int number: Milestone ID
+ :param str user: Username
+ :param str repo: Repo name
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.labels.list_by_milestone',
+ user=user, repo=repo, number=number)
+ return self._get_result(request)
diff --git a/pygithub3/services/issues/milestones.py b/pygithub3/services/issues/milestones.py
new file mode 100644
index 0000000..ac43a0c
--- /dev/null
+++ b/pygithub3/services/issues/milestones.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from pygithub3.services.base import Service
+
+
+class Milestones(Service):
+ """ Consume `Milestones API
+ <http://developer.github.com/v3/issues/milestones>`_ """
+
+ def list(self, user=None, repo=None, state='open', sort='due_date',
+ direction='desc'):
+ """ List milestones for a repo
+
+ :param str user: Username
+ :param str repo: Repo name
+ :param str state: 'open' or 'closed'
+ :param str sort: 'due_date' or 'completeness'
+ :param str direction: 'asc' or 'desc'
+ :returns: A :doc:`result`
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.milestones.list', user=user,
+ repo=repo)
+ return self._get_result(request, state=state, sort=sort,
+ direction=direction)
+
+ def get(self, number, user=None, repo=None):
+ """ Get a single milestone
+
+ :param int number: Milestone number
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.milestones.get', user=user,
+ repo=repo, number=number)
+ return self._get(request)
+
+ def create(self, data, user=None, repo=None):
+ """ Create a milestone
+
+ :param dict data: Input. See `github milestones doc`_
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ self._normalize_date('due_on', data)
+ request = self.make_request('issues.milestones.create', user=user,
+ repo=repo, body=data)
+ return self._post(request)
+
+ def update(self, number, data, user=None, repo=None):
+ """ Update a milestone
+
+ :param int number: Milestone number
+ :param dict data: Input. See `github milestones doc`_
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ self._normalize_date('due_on', data)
+ request = self.make_request('issues.milestones.update', user=user,
+ repo=repo, number=number, body=data)
+ return self._patch(request)
+
+ def delete(self, number, user=None, repo=None):
+ """ Delete a milestone
+
+ :param int number: Milestone number
+ :param str user: Username
+ :param str repo: Repo name
+
+ .. warning::
+ You must be authenticated
+
+ .. note::
+ Remember :ref:`config precedence`
+ """
+ request = self.make_request('issues.milestones.delete', user=user,
+ repo=repo, number=number)
+ self._delete(request)
diff --git a/pygithub3/tests/resources/test_issues.py b/pygithub3/tests/resources/test_issues.py
new file mode 100644
index 0000000..ae572af
--- /dev/null
+++ b/pygithub3/tests/resources/test_issues.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from unittest import TestCase
+
+from pygithub3.resources.issues import Label
+
+
+class TestLabel(TestCase):
+ def test_is_valid_color(self):
+ valid_colors = ['BADa55', 'FF42FF', '45DFCA']
+ for color in valid_colors:
+ self.assertTrue(Label.is_valid_color(color))
+
+ invalid_colors = ['BDA55', '#FFAABB', 'FFf']
+ for color in invalid_colors:
+ self.assertFalse(Label.is_valid_color(color))
diff --git a/pygithub3/tests/services/test_core.py b/pygithub3/tests/services/test_core.py
index 8a2bbbe..556f3d9 100644
--- a/pygithub3/tests/services/test_core.py
+++ b/pygithub3/tests/services/test_core.py
@@ -1,8 +1,9 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
-import requests
+from datetime import datetime
+import requests
from mock import patch
from pygithub3.tests.utils.core import TestCase
@@ -54,6 +55,16 @@ class TestServiceCalls(TestCase):
self.assertFalse(request_method.called)
self.assertIsInstance(result, base.Result)
+ def test_normalize_ok(self, request_method):
+ data = {'test': datetime(2012, 12, 12, 3, 3, 3)}
+ self.s._normalize_date('test', data)
+ self.assertEqual(data['test'], '2012-12-12T03:03:03Z')
+
+ def test_normalize_fail(self, request_method):
+ data = {'test': 'fail'}
+ self.s._normalize_date('test', data)
+ self.assertEqual(data['test'], 'fail')
+
@patch.object(requests.sessions.Session, 'request')
class TestMimeType(TestCase):
diff --git a/pygithub3/tests/services/test_issues.py b/pygithub3/tests/services/test_issues.py
new file mode 100644
index 0000000..2d42405
--- /dev/null
+++ b/pygithub3/tests/services/test_issues.py
@@ -0,0 +1,233 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+import requests
+from mock import patch, Mock
+
+from pygithub3.exceptions import ValidationError
+from pygithub3.tests.utils.core import TestCase
+from pygithub3.resources.base import json
+from pygithub3.services.issues import (Issue, Comments, Events, Labels,
+ Milestones)
+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(user='octocat', repo='Hello-World')
+
+ 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().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(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(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(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(user='octocat', repo='Hello-World')
+
+ def test_LIST(self, request_method):
+ request_method.return_value = mock_response_result()
+ self.cs.list(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(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(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(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(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(user='octocat', repo='Hello-World')
+
+ def test_LIST_by_issue(self, request_method):
+ request_method.return_value = mock_response_result()
+ self.ev.list_by_issue(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().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(1)
+ self.assertEqual(request_method.call_args[0],
+ ('get', _('repos/octocat/Hello-World/issues/events/1')))
+
+
+@patch.object(requests.sessions.Session, 'request')
+class TestLabelsService(TestCase):
+
+ def setUp(self):
+ self.lb = Labels(user='octocat', repo='Hello-World')
+
+ def test_GET(self, request_method):
+ request_method.return_value = mock_response()
+ self.lb.get('bug')
+ self.assertEqual(request_method.call_args[0],
+ ('get', _('repos/octocat/Hello-World/labels/bug')))
+
+ def test_CREATE(self, request_method):
+ request_method.return_value = mock_response('post')
+ self.lb.create(dict(name='bug', color='FF0000'))
+ self.assertEqual(request_method.call_args[0],
+ ('post', _('repos/octocat/Hello-World/labels')))
+
+ def test_CREATE_with_invalid_color(self, request_method):
+ request_method.return_value = mock_response('post')
+ # invalid color
+ with self.assertRaises(ValidationError):
+ args={'name': 'bug', 'color': 'FF00'}
+ self.lb.create(args)
+
+ def test_UPDATE(self, request_method):
+ request_method.return_value = mock_response('patch')
+ self.lb.update('bug', dict(name='critical', color='FF0000'))
+ self.assertEqual(request_method.call_args[0],
+ ('patch', _('repos/octocat/Hello-World/labels/bug')))
+
+ def test_UPDATE_with_invalid_color(self, request_method):
+ request_method.return_value = mock_response('post')
+ # invalid color
+ with self.assertRaises(ValidationError):
+ args={'name': 'critical',
+ 'color': 'FF00',}
+ self.lb.update('bug', args)
+
+ def test_DELETE(self, request_method):
+ request_method.return_value = mock_response('delete')
+ self.lb.delete('bug')
+ self.assertEqual(request_method.call_args[0],
+ ('delete', _('repos/octocat/Hello-World/labels/bug')))
+
+ def test_LIST_by_issue(self, request_method):
+ request_method.return_value = mock_response()
+ self.lb.list_by_issue(1)
+ self.assertEqual(request_method.call_args[0],
+ ('get', _('repos/octocat/Hello-World/issues/1/labels')))
+
+ def test_ADD_to_issue(self, request_method):
+ request_method.return_value = mock_response('post')
+ self.lb.add_to_issue(1, ('bug', 'critical'))
+ self.assertEqual(request_method.call_args[0],
+ ('post', _('repos/octocat/Hello-World/issues/1/labels')))
+
+ def test_REMOVE_from_issue(self, request_method):
+ request_method.return_value = mock_response('delete')
+ self.lb.remove_from_issue(1, 'bug')
+ self.assertEqual(request_method.call_args[0],
+ ('delete', _('repos/octocat/Hello-World/issues/1/labels/bug')))
+
+ def test_REPLACE_all(self, request_method):
+ self.lb.replace_all(1, ['bug', 'critical'])
+ self.assertEqual(request_method.call_args[0],
+ ('put', _('repos/octocat/Hello-World/issues/1/labels')))
+
+ def test_REMOVE_all(self, request_method):
+ request_method.return_value = mock_response('delete')
+ self.lb.remove_all(1)
+ self.assertEqual(request_method.call_args[0],
+ ('delete', _('repos/octocat/Hello-World/issues/1/labels')))
+
+ def test_LIST_by_milestone(self, request_method):
+ request_method.return_value = mock_response_result()
+ self.lb.list_by_milestone(1).all()
+ self.assertEqual(request_method.call_args[0],
+ ('get', _('repos/octocat/Hello-World/milestones/1/labels')))
+
+
+@patch.object(requests.sessions.Session, 'request')
+class TestMilestonesService(TestCase):
+
+ def setUp(self):
+ self.mi = Milestones(user='octocat', repo='Hello-World')
+
+ def test_LIST_by_repo(self, request_method):
+ request_method.return_value = mock_response_result()
+ self.mi.list().all()
+ self.assertEqual(request_method.call_args[0],
+ ('get', _('repos/octocat/Hello-World/milestones')))
+
+ def test_GET(self, request_method):
+ request_method.return_value = mock_response()
+ self.mi.get(1)
+ self.assertEqual(request_method.call_args[0],
+ ('get', _('repos/octocat/Hello-World/milestones/1')))
+
+ def test_CREATE(self, request_method):
+ request_method.return_value = mock_response('post')
+ self.mi.create(dict(title='test'))
+ self.assertEqual(request_method.call_args[0],
+ ('post', _('repos/octocat/Hello-World/milestones')))
+
+ def test_UPDATE(self, request_method):
+ request_method.return_value = mock_response('patch')
+ self.mi.update(1, dict(title='test'))
+ self.assertEqual(request_method.call_args[0],
+ ('patch', _('repos/octocat/Hello-World/milestones/1')))
+
+ def test_DELETE(self, request_method):
+ request_method.return_value = mock_response('delete')
+ self.mi.delete(1)
+ self.assertEqual(request_method.call_args[0],
+ ('delete', _('repos/octocat/Hello-World/milestones/1')))