Repository: auth0/auth0-python
Pull request #110 (2018-06-19 16:44:43)

Add pagination to Client Grants, Resource Servers and Rules
### What
This PR adds pagination support on MGMT API entities that were missing. 

I kept this free from breaking changes by providing an `include_totals=False` value (since if that's true, the response is an Object rather than an Array).

### Scope
- [x] Clients Grants
- [ ] User Grants (Missing entity, not going to be implemented)
- [x] Resource Servers
- [x] Rules

### How it was tested
✅ Unit tests asserting the params with which the function was called. Similar to the other pagination PR.



codecov-io: # [Codecov](https://codecov.io/gh/auth0/auth0-python/pull/110?src=pr&el=h1) Report
> Merging [#110](https://codecov.io/gh/auth0/auth0-python/pull/110?src=pr&el=desc) into [master](https://codecov.io/gh/auth0/auth0-python/commit/fce49889bf0a938b33dae02087da74f561e638a0?src=pr&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/auth0/auth0-python/pull/110/graphs/tree.svg?token=NrHTD7PDwQ&src=pr&height=150&width=650)](https://codecov.io/gh/auth0/auth0-python/pull/110?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #110      +/-   ##
==========================================
+ Coverage   93.46%   93.47%   +0.01%     
==========================================
  Files          32       32              
  Lines         643      644       +1     
==========================================
+ Hits          601      602       +1     
  Misses         42       42
```


| [Impacted Files](https://codecov.io/gh/auth0/auth0-python/pull/110?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [auth0/v3/management/emails.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC9lbWFpbHMucHk=) | `94.73% <ø> (ø)` | :arrow_up: |
| [auth0/v3/management/tenants.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC90ZW5hbnRzLnB5) | `100% <ø> (ø)` | :arrow_up: |
| [auth0/v3/management/logs.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC9sb2dzLnB5) | `100% <ø> (ø)` | :arrow_up: |
| [auth0/v3/management/device\_credentials.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC9kZXZpY2VfY3JlZGVudGlhbHMucHk=) | `100% <ø> (ø)` | :arrow_up: |
| [auth0/v3/management/connections.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC9jb25uZWN0aW9ucy5weQ==) | `100% <ø> (ø)` | :arrow_up: |
| [auth0/v3/management/users.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC91c2Vycy5weQ==) | `100% <ø> (ø)` | :arrow_up: |
| [auth0/v3/management/clients.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC9jbGllbnRzLnB5) | `100% <ø> (ø)` | :arrow_up: |
| [auth0/v3/management/rules.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC9ydWxlcy5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [auth0/v3/management/client\_grants.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC9jbGllbnRfZ3JhbnRzLnB5) | `100% <100%> (ø)` | :arrow_up: |
| [auth0/v3/management/resource\_servers.py](https://codecov.io/gh/auth0/auth0-python/pull/110/diff?src=pr&el=tree#diff-YXV0aDAvdjMvbWFuYWdlbWVudC9yZXNvdXJjZV9zZXJ2ZXJzLnB5) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/auth0/auth0-python/pull/110?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/auth0/auth0-python/pull/110?src=pr&el=footer). Last update [fce4988...57ab8ee](https://codecov.io/gh/auth0/auth0-python/pull/110?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/auth0/v3/management/client_grants.py b/auth0/v3/management/client_grants.py
index e198d64..2f55709 100644
--- a/auth0/v3/management/client_grants.py
+++ b/auth0/v3/management/client_grants.py
@@ -24,15 +24,27 @@ class ClientGrants(object):
             return url + '/' + id
         return url
 
-    def all(self, audience=None):
+    def all(self, audience=None, page=None, per_page=None, include_totals=False):
         """Retrieves all client grants.
 
         Args:
-           audience (str, optional): URL Encoded audience of a Resource Server
-               to filter
+            audience (str, optional): URL encoded audience of a Resource Server
+                to filter
+
+            page (int, optional): The result's page number (zero based).
+
+            per_page (int, optional): The amount of entries per page.
+
+            include_totals (bool, optional): True if the query summary is
+                to be included in the result, False otherwise.
         """
 
-        params = {'audience': audience or None}
+        params = {
+            'audience': audience,
+            'page': page,
+            'per_page': per_page,
+            'include_totals': str(include_totals).lower()
+        }
 
         return self.client.get(self._url(), params=params)
 
@@ -62,6 +74,7 @@ class ClientGrants(object):
            id (str): The id of the client grant to modify.
 
            body (dict): Attributes to modify.
+              See: https://auth0.com/docs/api/management/v2#!/Client_Grants/patch_client_grants_by_id
         """
 
         return self.client.patch(self._url(id), data=body)
diff --git a/auth0/v3/management/clients.py b/auth0/v3/management/clients.py
index 81da393..fd4bcbc 100644
--- a/auth0/v3/management/clients.py
+++ b/auth0/v3/management/clients.py
@@ -36,7 +36,7 @@ class Clients(object):
               retrieve all fields.
 
            include_fields (bool, optional): True if the fields specified are
-              to be include in the result, False otherwise.
+              to be included in the result, False otherwise.
 
            page (int): The result's page number (zero based).
 
@@ -78,7 +78,7 @@ class Clients(object):
               retrieve all fields.
 
            include_fields (bool, optional): True if the fields specified are
-              to be include in the result, False otherwise.
+              to be included in the result, False otherwise.
         """
 
         params = {'fields': fields and ','.join(fields) or None,
diff --git a/auth0/v3/management/connections.py b/auth0/v3/management/connections.py
index 3ef2ad7..95cc4c4 100644
--- a/auth0/v3/management/connections.py
+++ b/auth0/v3/management/connections.py
@@ -35,7 +35,7 @@ class Connections(object):
               retrieve all fields.
 
            include_fields (bool, optional): True if the fields specified are
-              to be include in the result, False otherwise.
+              to be included in the result, False otherwise.
 
            page (int): The result's page number (zero based).
 
@@ -69,7 +69,7 @@ class Connections(object):
               retrieve all fields.
 
            include_fields (bool, optional): True if the fields specified are
-              to be include in the result, False otherwise.
+              to be included in the result, False otherwise.
 
         Returns:
             A connection object.
diff --git a/auth0/v3/management/device_credentials.py b/auth0/v3/management/device_credentials.py
index 348907f..e3de825 100644
--- a/auth0/v3/management/device_credentials.py
+++ b/auth0/v3/management/device_credentials.py
@@ -39,7 +39,7 @@ class DeviceCredentials(object):
                 retrieve all fields
 
             include_fields (bool, optional): True if the fields specified are
-                to be excluded from the result, false otherwise
+                to be included in the result, False otherwise
                 (defaults to true)
         """
 
diff --git a/auth0/v3/management/emails.py b/auth0/v3/management/emails.py
index f85bad8..51ee677 100644
--- a/auth0/v3/management/emails.py
+++ b/auth0/v3/management/emails.py
@@ -33,7 +33,7 @@ class Emails(object):
                 to retrieve all fields.
 
             include_fields (bool, optional): True if the fields specified are
-                to be include in the result, False otherwise.
+                to be included in the result, False otherwise.
         """
         params = {'fields': fields and ','.join(fields) or None,
                   'include_fields': str(include_fields).lower()}
diff --git a/auth0/v3/management/logs.py b/auth0/v3/management/logs.py
index e6c7dc0..ca8c312 100644
--- a/auth0/v3/management/logs.py
+++ b/auth0/v3/management/logs.py
@@ -44,10 +44,10 @@ class Logs(object):
                 retrieve all fields.
 
             include_fields (bool, optional): True if the fields specified are
-                to be include in the result, False otherwise.
+                to be included in the result, False otherwise.
 
-            include_totals (bool, optional): true if a query summary must be
-                included in the result, false otherwise. Default false.
+            include_totals (bool, optional): True if the query summary is
+                to be included in the result, False otherwise.
 
             from_param (str, optional): Log Event Id to start retrieving logs. You can
                 limit the amount of logs using the take parameter
diff --git a/auth0/v3/management/resource_servers.py b/auth0/v3/management/resource_servers.py
index 5ffcb5b..daec168 100644
--- a/auth0/v3/management/resource_servers.py
+++ b/auth0/v3/management/resource_servers.py
@@ -34,11 +34,25 @@ class ResourceServers(object):
 
         return self.client.post(self._url(), data=body)
 
-    def get_all(self):
+    def get_all(self, page=None, per_page=None, include_totals=False):
         """Retrieves all resource servers
+
+        Args:
+            page (int, optional): The result's page number (zero based).
+
+            per_page (int, optional): The amount of entries per page.
+
+            include_totals (bool, optional): True if the query summary is
+                to be included in the result, False otherwise.
         """
 
-        return self.client.get(self._url())
+        params = {
+            'page': page,
+            'per_page': per_page,
+            'include_totals': str(include_totals).lower()
+        }
+
+        return self.client.get(self._url(), params=params)
 
     def get(self, id):
         """Retrieves a resource server by its id.
@@ -65,6 +79,7 @@ class ResourceServers(object):
            id (str): The id of the resource server to update.
 
            body (dict): Attributes to modify.
+              See: https://auth0.com/docs/api/management/v2#!/Resource_Servers/patch_resource_servers_by_id
         """
 
         return self.client.patch(self._url(id), data=body)
diff --git a/auth0/v3/management/rules.py b/auth0/v3/management/rules.py
index 1ab57b6..65e98b9 100644
--- a/auth0/v3/management/rules.py
+++ b/auth0/v3/management/rules.py
@@ -25,10 +25,13 @@ class Rules(object):
         return url
 
     def all(self, stage='login_success', enabled=True, fields=None,
-            include_fields=True):
+            include_fields=True, page=None, per_page=None, include_totals=False):
         """Retrieves a list of all rules.
 
         Args:
+            stage (str, optional):  Retrieves rules that match the execution
+                stage (defaults to login_success).
+
             enabled (bool, optional): If provided, retrieves rules that match
                 the value, otherwise all rules are retrieved.
 
@@ -40,14 +43,24 @@ class Rules(object):
                 to be included in the result, False otherwise
                 (defaults to true).
 
-            stage (str, optional):  Retrieves rules that match the execution
-                stage (defaults to login_success).
+            page (int, optional): The result's page number (zero based).
+
+            per_page (int, optional): The amount of entries per page.
+
+            include_totals (bool, optional): True if the query summary is
+                to be included in the result, False otherwise.
         """
 
-        params = {'fields': fields and ','.join(fields) or None,
-                  'include_fields': str(include_fields).lower(),
-                  'stage': stage}
+        params = {
+            'stage': stage,
+            'fields': fields and ','.join(fields) or None,
+            'include_fields': str(include_fields).lower(),
+            'page': page,
+            'per_page': per_page,
+            'include_totals': str(include_totals).lower()
+        }
 
+        # since the default is True, this is here to disable the filter
         if enabled != None:
             params['enabled'] = str(enabled).lower()
 
@@ -58,7 +71,7 @@ class Rules(object):
 
         Args:
             body (dict): Attributes for the newly created rule,
-                please see: https://auth0.com/docs/api/v2#!/Rules/post_rules
+                See: https://auth0.com/docs/api/v2#!/Rules/post_rules
         """
         return self.client.post(self._url(), data=body)
 
@@ -94,6 +107,7 @@ class Rules(object):
         Args:
             id (str): The id of the rule to modify.
 
-            body (dict): Please see: https://auth0.com/docs/api/v2#!/Rules/patch_rules_by_id
+            body (dict): Attributes to modify.
+                See: https://auth0.com/docs/api/v2#!/Rules/patch_rules_by_id
         """
         return self.client.patch(self._url(id), data=body)
diff --git a/auth0/v3/management/tenants.py b/auth0/v3/management/tenants.py
index 72a1b5e..a2456d0 100644
--- a/auth0/v3/management/tenants.py
+++ b/auth0/v3/management/tenants.py
@@ -30,7 +30,7 @@ class Tenants(object):
               retrieve all fields.
 
            include_fields (bool, optional): True if the fields specified are
-              to be include in the result, False otherwise.
+              to be included in the result, False otherwise.
         """
 
         params = {'fields': fields and ','.join(fields) or None,
diff --git a/auth0/v3/management/users.py b/auth0/v3/management/users.py
index f82425e..0ea5acf 100644
--- a/auth0/v3/management/users.py
+++ b/auth0/v3/management/users.py
@@ -90,7 +90,7 @@ class Users(object):
                 retrieve all fields.
 
             include_fields (bool, optional): True if the fields specified are
-                to be include in the result, False otherwise.
+                to be included in the result, False otherwise.
         """
         params = {
             'fields': fields and ','.join(fields) or None,
@@ -191,8 +191,8 @@ class Users(object):
                 where order is 1 for ascending and -1 for descending.
                 For example date:-1
 
-            include_totals (bool, optional): True if a query summary are
-                to be include in the result, False otherwise.
+            include_totals (bool, optional): True if the query summary is
+                to be included in the result, False otherwise.
 
             See: https://auth0.com/docs/api/management/v2#!/Users/get_logs_by_user
         """


Test patch:
diff --git a/auth0/v3/test/management/test_client_grants.py b/auth0/v3/test/management/test_client_grants.py
index 80291a3..5613ea0 100644
--- a/auth0/v3/test/management/test_client_grants.py
+++ b/auth0/v3/test/management/test_client_grants.py
@@ -10,19 +10,45 @@ class TestClientGrants(unittest.TestCase):
         mock_instance = mock_rc.return_value
 
         c = ClientGrants(domain='domain', token='jwttoken')
+
+        # With default params
         c.all()
 
         args, kwargs = mock_instance.get.call_args
 
         self.assertEqual('https://domain/api/v2/client-grants', args[0])
-        self.assertEqual(kwargs['params'], {'audience': None})
+        self.assertEqual(kwargs['params'], {
+            'audience': None,
+            'page': None,
+            'per_page': None,
+            'include_totals': 'false'
+        })
+
+        # With audience
+        c.all(audience='http://domain.auth0.com/api/v2/')
+
+        args, kwargs = mock_instance.get.call_args
+
+        self.assertEqual('https://domain/api/v2/client-grants', args[0])
+        self.assertEqual(kwargs['params'], {
+            'audience': 'http://domain.auth0.com/api/v2/',
+            'page': None,
+            'per_page': None,
+            'include_totals': 'false'
+        })
 
-        c.all(audience="http://domain.auth0.com/api/v2/")
+        # With pagination params
+        c.all(per_page=23, page=7, include_totals=True)
 
         args, kwargs = mock_instance.get.call_args
 
         self.assertEqual('https://domain/api/v2/client-grants', args[0])
-        self.assertEqual(kwargs['params'], {'audience': 'http://domain.auth0.com/api/v2/'})
+        self.assertEqual(kwargs['params'], {
+            'audience': None,
+            'page': 7,
+            'per_page': 23,
+            'include_totals': 'true'
+        })
 
     @mock.patch('auth0.v3.management.client_grants.RestClient')
     def test_create(self, mock_rc):
diff --git a/auth0/v3/test/management/test_resource_servers.py b/auth0/v3/test/management/test_resource_servers.py
index 74f8f7c..f760372 100644
--- a/auth0/v3/test/management/test_resource_servers.py
+++ b/auth0/v3/test/management/test_resource_servers.py
@@ -23,10 +23,28 @@ class TestResourceServers(unittest.TestCase):
 
         r = ResourceServers(domain='domain', token='jwttoken')
 
+        # with default params
         r.get_all()
 
         mock_instance.get.assert_called_with(
-            'https://domain/api/v2/resource-servers'
+            'https://domain/api/v2/resource-servers',
+            params={
+                'page': None,
+                'per_page': None,
+                'include_totals': 'false'
+            }
+        )
+
+        # with pagination params
+        r.get_all(page=3, per_page=27, include_totals=True)
+
+        mock_instance.get.assert_called_with(
+            'https://domain/api/v2/resource-servers',
+            params={
+                'page': 3,
+                'per_page': 27,
+                'include_totals': 'true'
+            }
         )
 
     @mock.patch('auth0.v3.management.resource_servers.RestClient')
diff --git a/auth0/v3/test/management/test_rules.py b/auth0/v3/test/management/test_rules.py
index e4cc339..d57da95 100644
--- a/auth0/v3/test/management/test_rules.py
+++ b/auth0/v3/test/management/test_rules.py
@@ -10,6 +10,8 @@ class TestRules(unittest.TestCase):
         mock_instance = mock_rc.return_value
 
         c = Rules(domain='domain', token='jwttoken')
+
+        # with default params
         c.all()
 
         args, kwargs = mock_instance.get.call_args
@@ -18,8 +20,12 @@ class TestRules(unittest.TestCase):
         self.assertEqual(kwargs['params'], {'fields': None,
                                             'include_fields': 'true',
                                             'enabled': 'true',
-                                            'stage': 'login_success'})
+                                            'stage': 'login_success',
+                                            'page': None,
+                                            'per_page': None,
+                                            'include_totals': 'false'})
 
+        # with stage and fields params
         c.all(stage='stage', enabled=False, fields=['a', 'b'],
               include_fields=False)
 
@@ -29,17 +35,24 @@ class TestRules(unittest.TestCase):
         self.assertEqual(kwargs['params'], {'fields': 'a,b',
                                             'include_fields': 'false',
                                             'enabled': 'false',
-                                            'stage': 'stage'})
+                                            'stage': 'stage',
+                                            'page': None,
+                                            'per_page': None,
+                                            'include_totals': 'false'})
 
-        c.all(stage='stage', enabled=None, fields=['a', 'b'],
-              include_fields=False)
+        # with pagination params
+        c.all(page=3, per_page=27, include_totals=True)
 
         args, kwargs = mock_instance.get.call_args
 
         self.assertEqual('https://domain/api/v2/rules', args[0])
-        self.assertEqual(kwargs['params'], {'fields': 'a,b',
-                                            'include_fields': 'false',
-                                            'stage': 'stage'})
+        self.assertEqual(kwargs['params'], {'fields': None,
+                                            'include_fields': 'true',
+                                            'enabled': 'true',
+                                            'stage': 'login_success',
+                                            'page': 3,
+                                            'per_page': 27,
+                                            'include_totals': 'true'})
 
     @mock.patch('auth0.v3.management.rules.RestClient')
     def test_create(self, mock_rc):