Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preliminary pagination support API #61

Merged
merged 3 commits into from Jan 10, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 49 additions & 0 deletions docs/index.rst
Expand Up @@ -169,6 +169,55 @@ You can also specify filters

tasks = api.tasks.list(project=1)

By default list returns all objects, eventually getting the
paginated results behind the scenes.

Pagination
^^^^^^^^^^

Pagination is controlled by three parameters as explained below:

+------------------+----------------------------+-------------+--------------------------------------------------------+
|`pagination` | `page_size` (default: 100) | `page` | Output |
+==================+============================+=============+========================================================+
| `True` (default) | `<integer>` | `None` | All results retrieved by using paginated results and |
| | | | loading them behind the scenes, using given page |
| | | | size (higher page size could yield better performances)|
+------------------+----------------------------+-------------+--------------------------------------------------------+
| `True` (default) | `<integer>` | `<integer>` | Only results for the given page of the given size |
| | | | are retrieved |
+------------------+----------------------------+-------------+--------------------------------------------------------+
| `False` | `unused` | `unused` | Current behavior: all results, ignoring pagination |
+------------------+----------------------------+-------------+--------------------------------------------------------+


.. note:: non numerical or false `page_size` values is casted to the default value

Examples
^^^^^^^^^

**No pagination**

.. code:: python

tasks = api.tasks.list(paginate=False)

.. warning:: be aware that if the unpaginated results may exceed

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if?

the data the parser can handle and may result in an error.

**Retrieve a single page**

.. code:: python

tasks_page_1 = api.tasks.list(page=1) # Will only return page 1

**Specify the page size**

.. code:: python

tasks_page_1 = api.tasks.list(page=1, page_size=200) # Will 200 results from page 1


Attach a file
~~~~~~~~~~~~~

Expand Down
50 changes: 47 additions & 3 deletions taiga/models/base.py
Expand Up @@ -34,11 +34,55 @@ def __init__(self, requester):

class ListResource(Resource):

def list(self, **queryparams):
def list(self, pagination=True, page_size=None, page=None, **queryparams):
"""
Retrieves a list of objects.

By default uses local cache and remote pagination

If pagination is used and no page is requested (the default), all the
remote objects are retrieved and appended in a single list.

If pagination is disabled, all the objects are fetched from the
endpojnt and returned. This may trigger some parsing error if the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

endpoint

result set is very large.

:param pagination: Use pagination (default: `True`)
:param page_size: Size of the pagination page (default: `100`).
Any non numeric value will be casted to the
default value
:param page: Page number to retrieve (default: `None`). Ignored if
`pagination` is `False`
:param queryparams: Additional filter parameters as accepted by the
remote API
:return: <SearchableList>
"""
if page_size and pagination:
try:
page_size = int(page_size)
except (ValueError, TypeError):
page_size = 100
queryparams['page_size'] = page_size
result = self.requester.get(
self.instance.endpoint, query=queryparams
self.instance.endpoint, query=queryparams, paginate=pagination
)
objects = self.parse_list(result.json())
objects = SearchableList()
objects.extend(self.parse_list(result.json()))
if result.headers.get('X-Pagination-Next', False) and not page:
next_page = 2
else:
next_page = None
while next_page:
pageparams = queryparams.copy()
pageparams['page'] = next_page
result = self.requester.get(
self.instance.endpoint, query=pageparams,
)
objects.extend(self.parse_list(result.json()))
if result.headers.get('X-Pagination-Next', False):
next_page += 1
else:
next_page = None
return objects

def get(self, resource_id):
Expand Down
20 changes: 13 additions & 7 deletions taiga/requestmaker.py
Expand Up @@ -7,7 +7,7 @@
from requests.packages.urllib3.exceptions import InsecureRequestWarning


def _disable_pagination():
def _requests_compatible_true():
if LooseVersion(requests.__version__) >= LooseVersion('2.11.0'):
return 'True'
else:
Expand Down Expand Up @@ -61,12 +61,15 @@ def __init__(self,
api_path, host,
token,
token_type='Bearer',
tls_verify=True):
tls_verify=True,
enable_pagination=True
):
self.api_path = api_path
self.host = host
self.token = token
self.token_type = token_type
self.tls_verify = tls_verify
self.enable_pagination = enable_pagination
self._cache = RequestCache()
if not self.tls_verify:
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
Expand All @@ -78,12 +81,15 @@ def cache(self):
def is_bad_response(self, response):
return 400 <= response.status_code <= 500

def headers(self):
def headers(self, paginate=True):
headers = {
'Content-type': 'application/json',
'Authorization': '{0} {1}'.format(self.token_type, self.token),
'x-disable-pagination': _disable_pagination()
}
if self.enable_pagination and paginate:
headers['x-lazy-pagination'] = _requests_compatible_true()
else:
headers['x-disable-pagination'] = _requests_compatible_true()
return headers

def urljoin(self, *parts):
Expand All @@ -96,7 +102,7 @@ def get_full_url(self, uri, query={}, **parameters):
)
return full_url

def get(self, uri, query={}, cache=False, **parameters):
def get(self, uri, query={}, cache=False, paginate=True, **parameters):
try:
full_url = self.urljoin(
self.host, self.api_path,
Expand All @@ -114,7 +120,7 @@ def get(self, uri, query={}, cache=False, **parameters):
if not result:
result = requests.get(
full_url,
headers=self.headers(),
headers=self.headers(paginate),
params=query,
verify=self.tls_verify
)
Expand All @@ -137,7 +143,7 @@ def post(self, uri, payload=None, query={}, files={}, **parameters):
if files:
headers = {
'Authorization': '{0} {1}'.format(self.token_type, self.token),
'x-disable-pagination': _disable_pagination()
'x-disable-pagination': _requests_compatible_true()
}
data = payload
else:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_requestmaker.py
@@ -1,4 +1,4 @@
from taiga.requestmaker import RequestMaker, _disable_pagination
from taiga.requestmaker import RequestMaker, _requests_compatible_true
import taiga.exceptions
import requests
import unittest
Expand Down Expand Up @@ -33,7 +33,7 @@ def test_call_requests_post_with_files(self, requests_post):
data=None, params={},
headers={
'Authorization': 'Bearer f4k3',
'x-disable-pagination': _disable_pagination()
'x-disable-pagination': _requests_compatible_true()
}
)

Expand Down