10000 Merge branch 'pr/114' into develop · Web5design/github3.py@acec937 · GitHub
[go: up one dir, main page]

Skip to content

Commit acec937

Browse files
committed
Merge branch 'pr/114' into develop
2 parents f82ceb4 + 0840bb8 commit acec937

File tree

7 files changed

+138
-24
lines changed

7 files changed

+138
-24
lines changed

github3/api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,10 @@ def iter_repo_issues(owner, repository, milestone=None, state=None,
174174
api-default: created
175175
:param str direction: accepted values: ('asc', 'desc')
176176
api-default: desc
177-
:param str since: ISO 8601 formatted timestamp, e.g.,
178-
2012-05-20T23:10:27Z
177+
:param since: (optional), Only issues after this date will
178+
be returned. This can be a `datetime` or an ISO8601 formatted
179+
date string, e.g., 2012-05-20T23:10:27Z
180+
:type since: datetime or string
179181
:param int number: (optional), number of issues to return.
180182
Default: -1 returns all issues
181183
:param str etag: (optional), ETag from a previous request to the same

github3/github.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from github3.users import User, Key
2020
from github3.decorators import requires_auth, requires_basic_auth
2121
from github3.notifications import Thread
22+
from github3.utils import timestamp_parameter
2223

2324

2425
class GitHub(GitHubCore):
@@ -493,7 +494,7 @@ def iter_notifications(self, all=False, participating=False, number=-1,
493494

494495
@requires_auth
495496
def iter_org_issues(self, name, filter='', state='', labels='', sort='',
496-
direction='', since='', number=-1, etag=None):
497+
direction='', since=None, number=-1, etag=None):
497498
"""Iterate over the organnization's issues if the authenticated user
498499
belongs to it.
499500
@@ -509,21 +510,24 @@ def iter_org_issues(self, name, filter='', state='', labels='', sort='',
509510
api-default: created
510511
:param str direction: accepted values: ('asc', 'desc')
511512
api-default: desc
512-
:param str since: ISO 8601 formatted timestamp, e.g.,
513-
2012-05-20T23:10:27Z
513+
:param since: (optional), Only issues after this date will
514+
be returned. This can be a `datetime` or an ISO8601 formatted
515+
date string, e.g., 2012-05-20T23:10:27Z
516+
:type since: datetime or string
514517
:param int number: (optional), number of issues to return. Default:
515518
-1, returns all available issues
516519
:param str etag: (optional), ETag from a previous request to the same
517520
endpoint
518521
:returns: generator of :class:`Issue <github3.issues.Issue>`
519522
"""
520523
url = self._build_url('orgs', name, 'issues')
524+
# issue_params will handle the since parameter
521525
params = issue_params(filter, state, labels, sort, direction, since)
522526
return self._iter(int(number), url, Issue, params, etag)
523527

524528
@requires_auth
525529
def iter_issues(self, filter='', state='', labels='', sort='',
526-
direction='', since='', number=-1, etag=None):
530+
direction='', since=None, number=-1, etag=None):
527531
"""List all of the authenticated user's (and organization's) issues.
528532
529533
:param str filter: accepted values:
@@ -537,21 +541,24 @@ def iter_issues(self, filter='', state='', labels='', sort='',
537541
api-default: created
538542
:param str direction: accepted values: ('asc', 'desc')
539543
api-default: desc
540-
:param str since: ISO 8601 formatted timestamp, e.g.,
541-
2012-05-20T23:10:27Z
544+
:param since: (optional), Only issues after this date will
545+
be returned. This can be a `datetime` or an ISO8601 formatted
546+
date string, e.g., 2012-05-20T23:10:27Z
547+
:type since: datetime or string
542548
:param int number: (optional), number of issues to return.
543549
Default: -1 returns all issues
544550
:param str etag: (optional), ETag from a previous request to the same
545551
endpoint
546552
:returns: generator of :class:`Issue <github3.issues.Issue>`
547553
"""
548554
url = self._build_url('issues')
555+
# issue_params will handle the since parameter
549556
params = issue_params(filter, state, labels, sort, direction, since)
550557
return self._iter(int(number), url, Issue, params, etag)
551558

552559
@requires_auth
553560
def iter_user_issues(self, filter='', state='', labels='', sort='',
554-
direction='', since='', number=-1, etag=None):
561+
direction='', since=None, number=-1, etag=None):
555562
"""List only the authenticated user's issues. Will not list
556563
organization's issues
557564
@@ -566,15 +573,18 @@ def iter_user_issues(self, filter='', state='', labels='', sort='',
566573
api-default: created
567574
:param str direction: accepted values: ('asc', 'desc')
568575
api-default: desc
569-
:param str since: ISO 8601 formatted timestamp, e.g.,
570-
2012-05-20T23:10:27Z
576+
:param since: (optional), Only issues after this date will
577+
be returned. This can be a `datetime` or an ISO8601 formatted
578+
date string, e.g., 2012-05-20T23:10:27Z
579+
:type since: datetime or string
571580
:param int number: (optional), number of issues to return.
572581
Default: -1 returns all issues
573582
:param str etag: (optional), ETag from a previous request to the same
574583
endpoint
575584
:returns: generator of :class:`Issue <github3.issues.Issue>`
576585
"""
577586
url = self._build_url('user', 'issues')
587+
# issue_params will handle the since parameter
578588
params = issue_params(filter, state, labels, sort, direction, since)
579589
return self._iter(int(number), url, Issue, params, etag)
580590

@@ -598,8 +608,10 @@ def iter_repo_issues(self, owner, repository, milestone=None,
598608
api-default: created
599609
:param str direction: accepted values: ('asc', 'desc')
600610
api-default: desc
601-
:param str since: ISO 8601 formatted timestamp, e.g.,
602-
2012-05-20T23:10:27Z
611+
:param since: (optional), Only issues after this date will
612+
be returned. This can be a `datetime` or an ISO8601 formatted
613+
date string, e.g., 2012-05-20T23:10:27Z
614+
:type since: datetime or string
603615
:param int number: (optional), number of issues to return.
604616
Default: -1 returns all issues
605617
:param str etag: (optional), ETag from a previous request to the same

github3/issues/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
from re import match
11+
from github3.utils import timestamp_parameter
1112
from .issue import Issue
1213

1314
__all__ = [Issue]
@@ -30,7 +31,8 @@ def issue_params(filter, state, labels, sort, direction, since):
3031
if direction in ('asc', 'desc'):
3132
params['direction'] = direction
3233

33-
if since and match('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$', since):
34+
since = timestamp_parameter(since)
35+
if since:
3436
params['since'] = since
3537

3638
return params

github3/repos/repo.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from json import dumps
1111
from base64 import b64encode
1212
from collections import Callable
13+
from datetime import datetime
1314
from github3.decorators import requires_auth
1415
from github3.events import Event
1516
from github3.git import Blob, Commit, Reference, Tag, Tree
@@ -31,6 +32,7 @@
3132
from github3.repos.stats import ContributorStats
3233
from github3.repos.tag import RepoTag
3334
from github3.users import User, Key
35+
from github3.utils import timestamp_parameter
3436

3537

3638
class Repository(GitHubCore):
@@ -966,8 +968,8 @@ def iter_commit_activity(self, number=-1, etag=None):
966968
url = self._build_url('stats', 'commit_activity', base_url=self._api)
967969
return self._iter(int(number), url, dict, etag=etag)
968970

969-
def iter_commits(self, sha=None, path=None, author=None, number=-1,
970-
etag=None):
971+
def iter_commits(self, sha=None, path=None, author=None, number=-1,
972+
etag=None, since=None, until=None):
971973
"""Iterate over commits in this repository.
972974
973975
:param str sha: (optional), sha or branch to start listing commits
@@ -980,9 +982,21 @@ def iter_commits(self, sha=None, path=None, author=None, number=-1,
980982
-1 returns all comments
981983
:param str etag: (optional), ETag from a previous request to the same
982984
endpoint
985+
:param since: (optional), Only commits after this date will
986+
be returned. This can be a `datetime` or an `ISO8601` formatted
987+
date string.
988+
:type since: datetime or string
989+
:param until: (optional), Only commits before this date will
990+
be returned. This can be a `datetime` or an `ISO8601` formatted
991+
date string.
992+
:type until: datetime or string
993+
983994
:returns: generator of :class:`RepoCommit <RepoCommit>`\ s
984995
"""
985-
params = {'sha': sha, 'path': path, 'author': author}
996+
params = {'sha': sha, 'path': path, 'author': author,
997+
'since': timestamp_parameter(since),
998+
'until': timestamp_parameter(until)}
999+
9861000
self._remove_none(params)
9871001
url = self._build_url('commits', base_url=self._api)
9881002
return self._iter(int(number), url, RepoCommit, params, etag)
@@ -1107,7 +1121,10 @@ def iter_issues(self,
11071121
'bug,ui,@high' :param sort: accepted values:
11081122
('created', 'updated', 'comments', 'created')
11091123
:param str direction: (optional), accepted values: ('asc', 'desc')
1110-
:param str since: (optional), ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ
1124+
:param since: (optional), Only issues after this date will
1125+
be returned. This can be a `datetime` or an `ISO8601` formatted
1126+
date string, e.g., 2012-05-20T23:10:27Z
1127+
:type since: datetime or string
11111128
:param int number: (optional), Number of issues to return.
11121129
By default all issues are returned
11131130
:param str etag: (optional), ETag from a previous request to the same
@@ -1220,24 +1237,24 @@ def iter_network_events(self, number=-1, etag=None):
12201237
return self._iter(int(number), url, Event, etag)
12211238

12221239
@requires_auth
1223-
def iter_notifications(self, all=False, participating=False, since='',
1240+
def iter_notifications(self, all=False, participating=False, since=None,
12241241
number=-1, etag=None):
12251242
"""Iterates over the notifications for this repository.
12261243
12271244
:param bool all: (optional), show all notifications, including ones
12281245
marked as read
12291246
:param bool participating: (optional), show only the notifications the
12301247
user is participating in directly
1231-
:param str since: (optional), filters out any notifications updated
1232-
before the given time. The time should be passed in as UTC in the
1233-
ISO 8601 format: ``YYYY-MM-DDTHH:MM:SSZ``. Example:
1234-
"2012-10-09T23:39:01Z".
1248+
:param since: (optional), filters out any notifications updated
1249+
before the given time. This can be a `datetime` or an `ISO8601` formatted
1250+
date string, e.g., 2012-05-20T23:10:27Z
1251+
:type since: datetime or string
12351252
:param str etag: (optional), ETag from a previous request to the same
12361253
endpoint
12371254
:returns: generator of :class:`Thread <github3.notifications.Thread>`
12381255
"""
12391256
url = self._build_url('notifications', base_url=self._api)
1240-
params = {'all': all, 'participating': participating, 'since': since}
1257+
params = {'all': all, 'participating': participating, 'since': timestamp_parameter(since)}
12411258
for (k, v) in list(params.items()):
12421259
if not v:
12431260
del params[k]

github3/utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from datetime import datetime
2+
import re
3+
4+
try:
5+
# python 2
6+
_base_string_class = basestring
7+
except NameError:
8+
# python 3: no basestring class any more, everything is a str
9+
_base_string_class = str
10+
11+
# with thanks to https://code.google.com/p/jquery-localtime/issues/detail?id=4
12+
ISO_8601 = re.compile("^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9])"
13+
"(T(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?"
14+
"(Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?)?$")
15+
16+
17+
def timestamp_parameter(timestamp, allow_none=True):
18+
19+
if timestamp is None:
20+
if allow_none:
21+
return None
22+
raise ValueError("Timestamp value cannot be None")
23+
24+
if isinstance(timestamp, datetime):
25+
return timestamp.isoformat()
26+
27+
if isinstance(timestamp, _base_string_class):
28+
if not ISO_8601.match(timestamp):
29+
raise ValueError("Invalid timestamp: %s is not a valid ISO-8601 formatted date" % timestamp)
30+
return timestamp
31+
32+
raise ValueError("Cannot accept type %s for timestamp" % type(timestamp))

tests/test_repos.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import github3
33
from github3 import repos
4+
from datetime import datetime
45
from tests.utils import (expect, BaseCase, load)
56
from mock import patch, mock_open
67
import sys
@@ -614,6 +615,18 @@ def test_iter_commits(self):
614615
c = next(self.repo.iter_commits('fakesha', '/'))
615616
self.mock_assertions()
616617

618+
since = datetime(2013, 6, 1, 0, 0, 0)
619+
until = datetime(2013, 6, 2, 0, 0, 0)
620+
self.conf = {'params': {'since': '2013-06-01T00:00:00', 'until': '2013-06-02T00:00:00'}}
621+
c = next(self.repo.iter_commits(since=since, until=until))
622+
self.mock_assertions()
623+
624+
since = '2013-06-01T00:00:00'
625+
until = '2013-06-02T00:00:00'
626+
self.conf = {'params': {'since': '2013-06-01T00:00:00', 'until': '2013-06-02T00:00:00'}}
627+
c = next(self.repo.iter_commits(since=since, until=until))
628+
self.mock_assertions()
629+
617630
def test_iter_contributors(self):
618631
self.response('user', _iter=True)
619632
self.get(self.api + 'contributors')

tests/test_utils.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from github3.utils import timestamp_parameter
2+
from tests.utils import BaseCase
3+
from datetime import datetime
4+
5+
6+
class TestTimestampParameter(BaseCase):
7+
8+
def test_datetimes(self):
9+
timestamp = datetime(2010, 6, 1, 12, 15, 30)
10+
self.assertEqual('2010-06-01T12:15:30', timestamp_parameter(timestamp))
11+
12+
def test_valid_datestring(self):
13+
testvals = (
14+
'2010-06-01',
15+
'2010-06-01T12:15:30',
16+
'2010-06-01T12:14:30.12321+02:00',
17+
'2010-06-01T12:14:30.12321-02:00',
18+
'2010-06-01T12:14:30.2115Z',
19+
)
20+
for timestamp in testvals:
21+
self.assertEqual(timestamp, timestamp_parameter(timestamp))
22+
23+
def test_invalid_datestring(self):
24+
testvals = (
25+
'2012-16-04',
26+
'2012-06-01v!',
27+
'fish',
28+
'2010-06-01T12:14:30.12321+02',
29+
'2010-06-01T12:70:30.12321+02',
30+
)
31+
for timestamp in testvals:
32+
self.assertRaises(ValueError, timestamp_parameter, timestamp)
33+
34+
def test_none_handling(self):
35+
self.assertTrue(timestamp_parameter(None, allow_none=True) is None)
36+
self.assertRaises(ValueError, timestamp_parameter, None, allow_none=False)

0 commit comments

Comments
 (0)
0