Advanced¶
Paging¶
Chinup supports transparent or explicit paging of Facebook data. To access all the response data, paging transparently, iterate over the Chinup object:
friends = ChinupBar(token='6Fq7Uy8J').get('me/friends')
for friend in friends:
print friend['name']
This will fetch additional pages as necessary to iterate over the entire list of friends. Listifying will also fetch all the pages:
friends = ChinupBar(token='6Fq7Uy8J').get('me/friends')
friends = list(friends)
Alternatively you can control paging explicitly by calling the
next_page
method. In that case, you should iterate on data
to avoid automatic paging:
friends = ChinupBar(token='6Fq7Uy8J').get('me/friends')
while friends:
for friend in friends.data:
print friend['name']
friends = friends.next_page()
ETags¶
The Facebook batch API supports ETags in requests and responses. See https://developers.facebook.com/docs/reference/ads-api/batch-requests/#etags
Chinup supports ETags transparently if you provide a suitable
settings.CACHE
, then chinup will automatically convert 304 responses to
the previously cached 200 response.
Inter-request dependencies¶
Chinup does not yet support inter-request dependencies with JSONpath. This is on the radar though! See https://developers.facebook.com/docs/graph-api/making-multiple-requests/#operations
raise_exceptions=False¶
If chinup encounters an error retrieving a response from the Facebook
Graph, the Chinup
object will raise its own exception whenever your
code attempts to access the response data. For large batch operations
where you’re expecting errors, you can avoid the exception by setting
raise_exceptions=False
:
chinups = [ChinupBar(user=u, raise_exceptions=False).get('me')
for u in User.objects.all()]
for c in chinups:
print "%s: %r" % (c.user, c.data or c.exception)
The above example uses raise_exceptions=False
to handle users that
don’t have associated tokens, or maybe their tokens have expired. The
data
attribute for those chinups will be None
and will not raise an
exception when accessed.
Testing¶
Chinup provides a mixin for Python unittest
. The mixin clears state
prior to each test, and provides assertBatches
to make sure your code
changes don’t adversely affect the batching of requests to Facebook. For
example:
from django.test import TestCase
from chinup.testing import ChinupTestMixin
from app import something
class MyTestCase(ChinupTestMixin, TestCase):
def test_something(self):
something()
# Calling something() should have resulted in two batches with
# a total of 30 requests.
self.assertBatches(2, 30)
Subclassing¶
While the Chinup
and ChinupBar
classes can be used out of the box,
they’re amenable to subclassing to support object and token lookup
according to the specifics of your project. The most prominent example is
the built-in support for django-allauth. If the allauth module can be
imported, then ChinupBar
will accept a user
parameter rather than
requiring a token
parameter.
Here’s another example, which layers on support for Facebook page tokens
from a separate table. Be sure to set ChinupBar.chinup_class
to your
subclass, as shown below.
import chinup
from chinup.exceptions import ChinupError, MissingToken
from myapp.models import Page
class NoSuchPage(ChinupError):
pass
class Chinup(chinup.Chinup):
def __init__(self, **kwargs):
self.page = kwargs.pop('page', None)
# Make sure there's only one token provider on this Chinup:
# page or user, not both.
assert not (self.page and kwargs.get('user'))
super(Chinup, self).__init__(**kwargs)
@classmethod
def prepare_batch(cls, chinups):
"""
Populates page tokens into chinups. This also immediately
"completes" any chinups which require a token that isn't
available, by setting chinup.exception.
"""
cls._fetch_pages(chinups)
cls._fetch_page_tokens(chinups)
# Weed out any chinups that didn't pass token stage.
chinups = [c for c in chinups if not c.completed]
return super(Chinup, cls).prepare_batch(chinups)
@classmethod
def _fetch_pages(cls, chinups):
"""
Replaces .page=PK with .page=OBJ for the chinups in the list.
If the PK can't be found, then sets NoSuchPage to be raised
when the chinup data is accessed.
"""
chinups = [c for c in chinups if not c.completed and not c.token
and isinstance(c.page, basestring)]
if chinups:
pages = Page.objects.filter(pk__in=set(c.page for c in chinups))
pages = {page.pk: page for page in pages}
for c in chinups:
page = pages.get(c.page)
if page:
c.page = page
else:
c.exception = NoSuchPage("No page with pk=%r" % c.page)
@classmethod
def _fetch_page_tokens(cls, chinups):
"""
Sets .token for the chinups in the list that have .page set.
If a token isn't available for the given page, then sets
MissingToken to be raised when the chinup data is accessed.
"""
chinups = [c for c in chinups if not c.completed and not c.token
and c.page]
if chinups:
page_tokens = PageToken.objects.filter(
account__page_id__in=set(c.page.pk for c in chinups),
)
page_tokens = page_tokens.select_related('account')
tokens = {pt.account.page_id: pt.token for pt in page_tokens}
for c in chinups:
token = tokens.get(c.page.pk)
if token:
c.token = token
else:
c.exception = MissingToken("No token for %r" % c.page)
class ChinupBar(chinup.ChinupBar):
chinup_class = Chinup
def __init__(self, **kwargs):
self.page = kwargs.pop('page', None)
# Make sure there's only one token provider on this ChinupBar:
# page or user, not both.
assert not (self.page and kwargs.get('user'))
super(ChinupBar, self).__init__(**kwargs)
def _get_chinup(self, **kwargs):
return super(ChinupBar, self)._get_chinup(
page=self.page,
**kwargs)