Source code for boxsdk.pagination.box_object_collection
# coding: utf-8
from __future__ import unicode_literals
from abc import ABCMeta, abstractmethod
import collections
from six import add_metaclass
from boxsdk.pagination.page import Page
[docs]@add_metaclass(ABCMeta)
class BoxObjectCollection(collections.Iterator, object):
"""
An iterator that represents a collection of Box objects (BaseObject).
A BoxObjectCollection instance contains everything it needs in order to retrieve and page through
responses from Box API endpoints that return collections of Box objects.
This class only has two public methods:
1). next(), which returns either a Page (sequence of BaseObjects) or individual BaseObjects based on
the constructor argument 'return_full_pages'.
2). next_pointer(), which returns the pointer (either an offset or a marker, based on the endpoint) that
will be used to retrieve the next page of Box objects. This pointer can be used when requesting new
BoxObjectCollection instances that start off from a particular page, instead of from the very beginning.
"""
_page_constructor = Page
def __init__(
self,
session,
url,
limit=None,
fields=None,
additional_params=None,
return_full_pages=False,
):
"""
:param session:
The Box session used to make requests.
:type session:
:class:`BoxSession`
:param url:
The endpoint url to hit.
:type url:
`unicode`
:param limit:
The number of entries for each page to return. The default, as well as the upper limit of this value,
differs by endpoint. See https://developer.box.com/reference. If limit is set to None, then the default
limit (returned by Box in the response) is used.
:type limit:
`int` or None
:param fields:
List of fields to request. If None, will return the default fields for the object.
:type fields:
`Iterable` of `unicode` or None
:param additional_params:
Additional HTTP params to send in the request.
:type additional_params:
`dict` or None
:param return_full_pages:
If True, then the returned iterator for this collection will return full pages of Box objects on each
call to next(). If False, the iterator will return a single Box object on each next() call.
:type return_full_pages:
`bool`
"""
super(BoxObjectCollection, self).__init__()
self._session = session
self._url = url
self._limit = limit
self._fields = fields
self._additional_params = additional_params
self._return_full_pages = return_full_pages
self._has_retrieved_all_items = False
self._all_items = None
[docs] def next(self):
"""
Returns either a Page (a Sequence of BaseObjects) or a BaseObject depending on self._return_full_pages.
Invoking this method may make an API call to Box. Any exceptions that can occur while making requests
may be raised in this method.
:rtype:
:class:`Page` or :class:`BaseObject`
"""
if self._all_items is None:
self._all_items = self._items_generator()
return next(self._all_items)
__next__ = next
def _items_generator(self):
"""
:rtype:
:class:`Page` or :class:`BaseObject`
"""
while not self._has_retrieved_all_items:
response_object = self._load_next_page()
self._update_pointer_to_next_page(response_object)
self._has_retrieved_all_items = not self._has_more_pages(response_object)
page = self._page_constructor(self._session, response_object)
if self._return_full_pages:
yield page
else:
# It's possible for the Box API to return 0 items in a page, even if there are more items to be
# retrieved on subsequent pages. When self._return_full_pages is True, then yielding a 0-item
# page is fine because that's what the page returned.
# But when we are iterating over individual items, and not pages, it's odd to yield a sequence of
# Nones (for that page that had 0 items). So instead, we continue to request more pages until we
# have Box objects to yield.
if not page:
continue
for entry in page:
yield entry
def _load_next_page(self):
"""
Request the next page of entries from Box. Raises any network-related exceptions, including BoxAPIException.
Returns a parsed dictionary of the JSON response from Box
:rtype:
`dict`
"""
params = {}
if self._limit is not None:
params['limit'] = self._limit
if self._fields:
params['fields'] = ','.join(self._fields)
if self._additional_params:
params.update(self._additional_params)
params.update(self._next_page_pointer_params())
box_response = self._session.get(self._url, params=params)
return box_response.json()
@abstractmethod
def _update_pointer_to_next_page(self, response_object):
"""
Update the internal pointer attribute of this class to what will be used to request the next page
of Box objects.
A "pointer" can either be a marker (for marker-based paging) or an offset (for limit-offset paging).
:param response_object:
The parsed HTTP response from Box after requesting more pages.
:type response_object:
`dict`
"""
raise NotImplementedError
@abstractmethod
def _has_more_pages(self, response_object):
"""
Are there more pages of entries to query Box for? This gets invoked after self._update_pointer_to_next_page().
:param response_object:
The parsed HTTP response from Box after requesting more pages.
:type response_object:
`dict`
:rtype:
`bool`
"""
raise NotImplementedError
@abstractmethod
def _next_page_pointer_params(self):
"""
The dict of HTTP params that specify which page of Box objects to retrieve.
:rtype:
`dict`
"""
raise NotImplementedError
[docs] @abstractmethod
def next_pointer(self):
"""
The pointer that will be used to request the next page of Box objects.
For limit-offset based paging, this is an offset. For marker-based paging, this is a marker.
The pointer only gets progressed upon successful page requests to Box.
:rtype:
varies
"""
raise NotImplementedError