diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 21a04b2..3c9c732 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -103,7 +103,7 @@ For merging, you should: 1. Include passing tests (run ``tox``) [1]_. 2. Update documentation when there's new API, functionality etc. -4. Add yourself to ``AUTHORS.rst``. +3. Add yourself to ``AUTHORS.rst``. .. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will `run the tests `_ for each change you add in the pull request. diff --git a/README.rst b/README.rst index 79e498a..24372fc 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ Overview :stub-columns: 1 * - tests - - | |travis| |pyup| + - | |travis| | |codeclimate| |codeclimate-tests| * - package - | |version| |wheel| @@ -24,10 +24,6 @@ Overview :alt: Travis-CI Build Status :target: https://travis-ci.org/khornberg/octokit.py -.. |pyup| image:: https://pyup.io/repos/github/khornberg/octokit.py/shield.svg - :target: https://pyup.io/repos/github/khornberg/octokit.py/ - :alt: Updates - .. |codeclimate| image:: https://codeclimate.com/github/khornberg/octokit.py/badges/gpa.svg :target: https://codeclimate.com/github/khornberg/octokit.py :alt: CodeClimate Quality Status diff --git a/setup.py b/setup.py index f443820..4ff05f7 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def read(*names, **kwargs): setup( name='octokitpy', - version='0.7.2', + version='0.8.0', license='MIT license', description='Python client for GitHub API', long_description='%s\n%s' % ( diff --git a/src/octokit/__init__.py b/src/octokit/__init__.py index 7961aae..b4b35b1 100644 --- a/src/octokit/__init__.py +++ b/src/octokit/__init__.py @@ -10,6 +10,8 @@ from jose import jwt from octokit import errors from octokit import utils +page_regex = re.compile(r'[\?\&]page=(\d+)[_&=\w\d]*>; rel="(\w+)"') + class Base(object): @@ -203,3 +205,26 @@ class Octokit(Base): return list((self._convert_to_object(value) for index, value in enumerate(item))) else: return item + + def set_pages(self, obj, previous_page_requested=None): + response_headers = obj._response.headers + links = response_headers.get('Link', None) + if links: + matches = re.findall(page_regex, links) + if matches: + for page, kind in matches: + setattr(obj, '{}_page'.format(kind), int(page)) + setattr(obj, 'pages', getattr(obj, 'last_page', previous_page_requested)) + setattr(obj, 'has_pages', True) + setattr(obj, 'current_page', previous_page_requested or getattr(obj, 'next_page') - 1) + setattr(obj, 'is_last_page', obj.pages == obj.current_page) + else: + setattr(obj, 'has_pages', False) + return obj + + def paginate(self, obj, page=1, **kwargs): + response = self.set_pages(obj(page=page, **kwargs)) + yield response.json + while not response.is_last_page: + response = self.set_pages(obj(page=response.next_page, **kwargs), response.next_page) + yield response.json diff --git a/src/octokit/data/rest.json b/src/octokit/data/rest.json index 051c8fe..60cf786 100644 --- a/src/octokit/data/rest.json +++ b/src/octokit/data/rest.json @@ -1854,6 +1854,15 @@ "accept": "application/vnd.github.machine-man-preview" }, "params": { + "page": { + "type": "number", + "description": "Page number of the results to fetch." + }, + "per_page": { + "type": "number", + "default": "30", + "description": "A custom page size up to 100. Default is 30." + }, "user_id": { "type": "string", "description": diff --git a/tests/test_octokit.py b/tests/test_octokit.py index 31d0b78..eb69a57 100644 --- a/tests/test_octokit.py +++ b/tests/test_octokit.py @@ -1,3 +1,26 @@ +class MockHeaders(object): + + def __init__(self, requested_page): + Link = 'Links ; rel="next", ; rel="last"'.format( # noqa E501 + min(requested_page + 1, 4) + ) + self.headers = {'Link': Link} + + +class MockObject(object): + + def __init__(self, page, kwargs): + self._response = MockHeaders(page) + self.json = {'page': page, 'kwargs': kwargs} + # self.is_last_page = True if page == 4 else False + # self.next_page = min(page + 1, 4) if page else 2 + + +def MockResponse(page=None, **kwargs): + print('mr', page, kwargs) + return MockObject(page, kwargs) + + class TestOctokit(object): def test_can_instantiate_class(self): @@ -15,3 +38,12 @@ class TestOctokit(object): def test_clients_are_lower_case(self): from octokit import Octokit assert all(client.islower() for client in Octokit.__dict__) + + def test_pagination(self): + from octokit import Octokit + sut_obj = MockResponse + p = Octokit().paginate(sut_obj, param='value') + assert next(p) == {'page': 1, 'kwargs': {'param': 'value'}} + assert next(p) == {'page': 2, 'kwargs': {'param': 'value'}} + assert next(p) == {'page': 3, 'kwargs': {'param': 'value'}} + assert next(p) == {'page': 4, 'kwargs': {'param': 'value'}}