*********** Collections *********** lazr.restful makes collections of data available through Pythonic mechanisms like slices. >>> from lazr.restfulclient.tests.example import CookbookWebServiceClient >>> service = CookbookWebServiceClient() You can iterate through all the items in a collection. >>> names = sorted([recipe.dish.name for recipe in service.recipes]) >>> len(names) 5 >>> names [u'Baked beans', ..., u'Roast chicken'] But it's almost always better to slice them. >>> sorted([recipe.dish.name for recipe in service.recipes[:2]]) [u'Roast chicken', u'Roast chicken'] You can get a slice of any collection, so long as you provide start and end points keyed to the beginning of the list. You can't key a slice to the end of the list because it might be expensive to calculate how big the list is. This set-up code creates a regular Python list of all recipes on the site, for comparison with a lazr.restful Collection object representing the same list. >>> all_recipes = [recipe for recipe in service.recipes] >>> recipes = service.recipes Calling len() on the Collection object makes sure that the first page of representations is cached, which forces this test to test an optimization. >>> ignored = len(recipes) These tests demonstrate that slicing the collection resource gives the same results as collecting all the entries in the collection, and slicing an ordinary list. >>> def slices_match(slice): ... """Slice two lists of recipes, then make sure they're the same.""" ... list1 = recipes[slice] ... list2 = all_recipes[slice] ... if len(list1) != len(list2): ... raise ("Lists are different sizes: %d vs. %d" % ... (len(list1), len(list2))) ... for index in range(0, len(list1)): ... if list1[index].id != list2[index].id: ... raise ("%s doesn't match %s in position %d" % ... (list1[index].id, list2[index].id, index)) ... return True >>> slices_match(slice(3)) True >>> slices_match(slice(50)) True >>> slices_match(slice(1,2)) True >>> slices_match(slice(2,21)) True >>> slices_match(slice(2,21,3)) True >>> slices_match(slice(0, 200)) True >>> slices_match(slice(30, 200)) True >>> slices_match(slice(60, 100)) True >>> recipes[5:] Traceback (most recent call last): ... ValueError: Collection slices must have a definite, nonnegative end point. >>> recipes[10:-1] Traceback (most recent call last): ... ValueError: Collection slices must have a definite, nonnegative end point. >>> recipes[-1:] Traceback (most recent call last): ... ValueError: Collection slices must have a nonnegative start point. >>> recipes[:] Traceback (most recent call last): ... ValueError: Collection slices must have a definite, nonnegative end point. You can slice a collection that's the return value of a named operation. >>> e_recipes = service.cookbooks.find_recipes(search='e') >>> len(e_recipes[1:3]) 2 You can also access individual items in this collection by index. >>> print e_recipes[1].dish.name Foies de voilaille en aspic >>> e_recipes[1000] Traceback (most recent call last): ... IndexError: list index out of range When are representations fetched? ================================= To avoid unnecessary HTTP requests, a representation of a collection is fetched at the last possible moment. Let's see what that means. >>> import httplib2 >>> httplib2.debuglevel = 1 >>> service = CookbookWebServiceClient() send: ... ... Just accessing a top-level collection doesn't trigger an HTTP request. >>> recipes = service.recipes >>> dishes = service.dishes >>> cookbooks = service.cookbooks Getting the length of the collection, or any entry from the collection, triggers an HTTP request. >>> len(recipes) send: 'GET /1.0/recipes... ... >>> dish = dishes[1] send: 'GET /1.0/dishes... ... Invoking a named operation will also trigger an HTTP request. >>> cookbooks.find_recipes(search="foo") send: ... ... Scoped collections work the same way: just getting a reference to the collection doesn't trigger an HTTP request. >>> recipes = dish.recipes But getting any information about the collection triggers an HTTP request. >>> len(recipes) send: 'GET /1.0/dishes/.../recipes ... ... Cleanup. >>> httplib2.debuglevel = None