Repository: fedora-infra/bodhi

Issue:
Python binding API list_overrides cannot return all overrides
`list_overrides` just returns overrides in the first page of `/overrides` by the default page size. Meanwhile, it does not accept argument, such as `page`, to return overrides from the rest pages.


Discussion:
`query` has parameters `rows_per_page` and `page`. These two could be added to `list_overrides` as well.

Resolving patch:
diff --git a/bodhi/client/__init__.py b/bodhi/client/__init__.py
--- a/bodhi/client/__init__.py
+++ b/bodhi/client/__init__.py
@@ -95,6 +95,15 @@ def _warn_if_url_and_staging_set(ctx, param, value):
     staging_option,
     url_option]
 
+# Basic options for pagination of query result
+pagination_options = [
+    click.option('--rows', default=None,
+                 type=click.IntRange(1, 100, clamp=False),
+                 help='Limits number of results shown per page'),
+    click.option('--page', default=None,
+                 type=click.IntRange(1, clamp=False),
+                 help='Go to page number')]
+
 
 def add_options(options):
     """
@@ -376,8 +385,9 @@ def edit(user, password, url, **kwargs):
 @click.option('--mine', is_flag=True, help='Show only your updates')
 @staging_option
 @url_option
+@add_options(pagination_options)
 @handle_errors
-def query(url, mine=False, **kwargs):
+def query(url, mine=False, rows=None, **kwargs):
     # User Docs that show in the --help
     """Query updates on Bodhi.
 
@@ -400,7 +410,7 @@ def query(url, mine=False, **kwargs):
     if mine:
         client.init_username()
         kwargs['user'] = client.username
-    resp = client.query(**kwargs)
+    resp = client.query(rows_per_page=rows, **kwargs)
     print_resp(resp, client)
 
 
@@ -606,9 +616,11 @@ def overrides():
 @click.option('--builds', default=None,
               help='Query by comma-separated build id(s)')
 @url_option
+@add_options(pagination_options)
 @handle_errors
 def query_buildroot_overrides(url, user=None, mine=False, packages=None,
-                              expired=None, releases=None, builds=None, **kwargs):
+                              expired=None, releases=None, builds=None,
+                              rows=None, page=None, **kwargs):
     # Docs that show in the --help
     """Query the buildroot overrides."""
     # Developer Docs
@@ -625,6 +637,8 @@ def query_buildroot_overrides(url, user=None, mine=False, packages=None,
         expired (bool): If supplied, True returns only expired overrides, False only active.
         releases (unicode): If supplied, the overrides for these releases are queried.
         builds (unicode): If supplied, the overrides for these builds are queried.
+        rows (unicode): The limit of rows displayed per page for query result.
+        page (unicode): If supplied, returns the results for a specific page number.
         kwargs (dict): Other keyword arguments passed to us by click.
     """
     client = bindings.BodhiClient(base_url=url, staging=kwargs['staging'])
@@ -632,7 +646,8 @@ def query_buildroot_overrides(url, user=None, mine=False, packages=None,
         client.init_username()
         user = client.username
     resp = client.list_overrides(user=user, packages=packages,
-                                 expired=expired, releases=releases, builds=builds)
+                                 expired=expired, releases=releases, builds=builds,
+                                 rows_per_page=rows, page=page)
     print_resp(resp, client)
 
 
diff --git a/bodhi/client/bindings.py b/bodhi/client/bindings.py
--- a/bodhi/client/bindings.py
+++ b/bodhi/client/bindings.py
@@ -413,7 +413,8 @@ def list_composes(self):
 
     @errorhandled
     def list_overrides(self, user=None, packages=None,
-                       expired=None, releases=None, builds=None):
+                       expired=None, releases=None, builds=None,
+                       rows_per_page=None, page=None):
         """
         List buildroot overrides.
 
@@ -425,6 +426,10 @@ def list_overrides(self, user=None, packages=None,
             releases (basestring): Comma separated Release shortnames to filter buildroot overrides
                 by.
             builds (basestring): Comma separated build NVRs to filter overrides by.
+            rows_per_page (int): Limit the results to a certain number of rows per page.
+                (default:None)
+            page (int): Return a specific page of results.
+                (default:None)
         """
         params = {}
         if user:
@@ -437,6 +442,10 @@ def list_overrides(self, user=None, packages=None,
             params['releases'] = releases
         if builds:
             params['builds'] = builds
+        if rows_per_page:
+            params['rows_per_page'] = rows_per_page
+        if page:
+            params['page'] = page
         return self.send_request('overrides/', verb='GET', params=params)
 
     def init_username(self):


Test patch:
diff --git a/bodhi/tests/client/test___init__.py b/bodhi/tests/client/test___init__.py
--- a/bodhi/tests/client/test___init__.py
+++ b/bodhi/tests/client/test___init__.py
@@ -508,7 +508,7 @@ def test_query_single_update(self, send_request):
                 'submitted_since': None, 'suggest': None, 'request': None, 'bugs': None,
                 'staging': False, 'modified_since': None, 'pushed': None, 'pushed_since': None,
                 'user': None, 'critpath': None, 'updateid': None, 'packages': None, 'type': None,
-                'cves': None})
+                'cves': None, 'rows_per_page': None, 'page': None})
 
     @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
                 mock.MagicMock(return_value='a_csrf_token'))
@@ -538,7 +538,7 @@ def test_query_multiple_update(self, send_request):
                 'submitted_since': None, 'suggest': None, 'request': None, 'bugs': None,
                 'staging': False, 'modified_since': None, 'pushed': None, 'pushed_since': None,
                 'user': None, 'critpath': None, 'updateid': None, 'packages': None, 'type': None,
-                'cves': None})
+                'cves': None, 'rows_per_page': None, 'page': None})
 
     @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
                 mock.MagicMock(return_value='a_csrf_token'))
@@ -568,7 +568,7 @@ def test_url_flag(self, send_request):
                 'submitted_since': None, 'suggest': None, 'request': None, 'bugs': None,
                 'staging': False, 'modified_since': None, 'pushed': None, 'pushed_since': None,
                 'user': None, 'critpath': None, 'updateid': None, 'packages': None, 'type': None,
-                'cves': None})
+                'cves': None, 'rows_per_page': None, 'page': None})
         self.assertEqual(bindings_client.base_url, 'http://localhost:6543/')
 
     @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
@@ -597,9 +597,63 @@ def test_query_mine_flag_username_unset(self, mock_input, send_request):
                 'submitted_since': None, 'suggest': None, 'request': None, 'bugs': None,
                 'staging': False, 'modified_since': None, 'pushed': None, 'pushed_since': None,
                 'user': 'dudemcpants', 'critpath': None, 'updateid': None, 'packages': None,
-                'type': None, 'cves': None})
+                'type': None, 'cves': None, 'rows_per_page': None, 'page': None})
         mock_open.assert_called_with(fedora.client.openidbaseclient.b_SESSION_FILE, 'rb')
 
+    @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
+                mock.MagicMock(return_value='a_csrf_token'))
+    @mock.patch('bodhi.client.bindings.BodhiClient.send_request',
+                return_value=client_test_data.EXAMPLE_QUERY_MUNCH, autospec=True)
+    def test_rows_flag(self, send_request):
+        """
+        Assert correct behavior with the --rows flag.
+        """
+        runner = testing.CliRunner()
+
+        result = runner.invoke(
+            client.query,
+            ['--rows', 10])
+
+        self.assertEqual(result.exit_code, 0)
+        bindings_client = send_request.mock_calls[0][1][0]
+        send_request.assert_called_once_with(
+            bindings_client, 'updates/', verb='GET',
+            params={
+                'approved_since': None, 'status': None, 'locked': None,
+                'builds': None, 'releases': None,
+                'content_type': None,
+                'submitted_since': None, 'suggest': None, 'request': None, 'bugs': None,
+                'staging': False, 'modified_since': None, 'pushed': None, 'pushed_since': None,
+                'user': None, 'critpath': None, 'updateid': None, 'packages': None, 'type': None,
+                'cves': None, 'rows_per_page': 10, 'page': None})
+
+    @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
+                mock.MagicMock(return_value='a_csrf_token'))
+    @mock.patch('bodhi.client.bindings.BodhiClient.send_request',
+                return_value=client_test_data.EXAMPLE_QUERY_MUNCH, autospec=True)
+    def test_page_flag(self, send_request):
+        """
+        Assert correct behavior with the --page flag.
+        """
+        runner = testing.CliRunner()
+
+        result = runner.invoke(
+            client.query,
+            ['--page', 5])
+
+        self.assertEqual(result.exit_code, 0)
+        bindings_client = send_request.mock_calls[0][1][0]
+        send_request.assert_called_once_with(
+            bindings_client, 'updates/', verb='GET',
+            params={
+                'approved_since': None, 'status': None, 'locked': None,
+                'builds': None, 'releases': None,
+                'content_type': None,
+                'submitted_since': None, 'suggest': None, 'request': None, 'bugs': None,
+                'staging': False, 'modified_since': None, 'pushed': None, 'pushed_since': None,
+                'user': None, 'critpath': None, 'updateid': None, 'packages': None, 'type': None,
+                'cves': None, 'rows_per_page': None, 'page': 5})
+
 
 class TestQueryBuildrootOverrides(unittest.TestCase):
     """
@@ -681,6 +735,46 @@ def _send_request(*args, **kwargs):
             mock.call(bindings_client, 'releases/', verb='GET',
                       params={'ids': [15]}))
 
+    @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
+                mock.MagicMock(return_value='a_csrf_token'))
+    @mock.patch('bodhi.client.bindings.BodhiClient.send_request',
+                return_value=client_test_data.EXAMPLE_QUERY_OVERRIDES_MUNCH, autospec=True)
+    def test_rows_flag(self, send_request):
+        """
+        Assert correct behavior with the --rows flag.
+        """
+        runner = testing.CliRunner()
+
+        result = runner.invoke(
+            client.query_buildroot_overrides,
+            ['--rows', 10])
+
+        self.assertEqual(result.exit_code, 0)
+        bindings_client = send_request.mock_calls[0][1][0]
+        send_request.assert_called_once_with(
+            bindings_client, 'overrides/', verb='GET',
+            params={'rows_per_page': 10})
+
+    @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
+                mock.MagicMock(return_value='a_csrf_token'))
+    @mock.patch('bodhi.client.bindings.BodhiClient.send_request',
+                return_value=client_test_data.EXAMPLE_QUERY_OVERRIDES_MUNCH, autospec=True)
+    def test_page_flag(self, send_request):
+        """
+        Assert correct behavior with the --page flag.
+        """
+        runner = testing.CliRunner()
+
+        result = runner.invoke(
+            client.query_buildroot_overrides,
+            ['--page', 5])
+
+        self.assertEqual(result.exit_code, 0)
+        bindings_client = send_request.mock_calls[0][1][0]
+        send_request.assert_called_once_with(
+            bindings_client, 'overrides/', verb='GET',
+            params={'page': 5})
+
 
 class TestRequest(unittest.TestCase):
     """
diff --git a/bodhi/tests/client/test_bindings.py b/bodhi/tests/client/test_bindings.py
--- a/bodhi/tests/client/test_bindings.py
+++ b/bodhi/tests/client/test_bindings.py
@@ -535,6 +535,32 @@ def test_with_builds(self):
         client.send_request.assert_called_once_with('overrides/', verb='GET',
                                                     params={'builds': 'python-1.5.6-3.fc26'})
 
+    def test_with_rows_per_page(self):
+        """
+        Test with the rows_per_page parameter.
+        """
+        client = bindings.BodhiClient()
+        client.send_request = mock.MagicMock(return_value='response')
+
+        response = client.list_overrides(rows_per_page=10)
+
+        self.assertEqual(response, 'response')
+        client.send_request.assert_called_once_with('overrides/', verb='GET',
+                                                    params={'rows_per_page': 10})
+
+    def test_with_page(self):
+        """
+        Test with the page parameter.
+        """
+        client = bindings.BodhiClient()
+        client.send_request = mock.MagicMock(return_value='response')
+
+        response = client.list_overrides(page=5)
+
+        self.assertEqual(response, 'response')
+        client.send_request.assert_called_once_with('overrides/', verb='GET',
+                                                    params={'page': 5})
+
 
 class TestBodhiClient_override_str(unittest.TestCase):
     """
@@ -764,6 +790,32 @@ def test_with_type_(self):
         client.send_request.assert_called_once_with(
             'updates/', verb='GET', params={'packages': 'bodhi', 'type': 'security'})
 
+    def test_with_rows_per_page(self):
+        """
+        Test with the 'rows_per_page' kwarg.
+        """
+        client = bindings.BodhiClient()
+        client.send_request = mock.MagicMock(return_value='return_value')
+
+        result = client.query(packages='bodhi', rows_per_page=10)
+
+        self.assertEqual(result, 'return_value')
+        client.send_request.assert_called_once_with(
+            'updates/', verb='GET', params={'packages': 'bodhi', 'rows_per_page': 10})
+
+    def test_with_page(self):
+        """
+        Test with the 'page' kwarg.
+        """
+        client = bindings.BodhiClient()
+        client.send_request = mock.MagicMock(return_value='return_value')
+
+        result = client.query(packages='bodhi', page=5)
+
+        self.assertEqual(result, 'return_value')
+        client.send_request.assert_called_once_with(
+            'updates/', verb='GET', params={'packages': 'bodhi', 'page': 5})
+
 
 class TestBodhiClient_save(unittest.TestCase):
     """