Repository: Shopify/shopify_python_api

Issue:
Cursor pagination issue with single page results
I was trying out the new cursor pagination 6.0.0 release and came across an issue.

page1 = shopify.Product.find(limit=100)
while page1.has_next_page():
   // do stuff

I can an error 
AttributeError: 'Collection' object has no attribute 'has_next_page'

This happens when I have less then limit results returned so there is only 1 page of results. It seems to return a Collection object instead of a PaginatedCollection.
If I reduce the limit so there will be at least 2 pages, it works fine.

For now I will use a limit I know will return multiple pages, but this means always making an extra api call.


Discussion:

Patch:
diff --git a/shopify/base.py b/shopify/base.py
index bcab899..65349e6 100644
--- a/shopify/base.py
+++ b/shopify/base.py
@@ -208,43 +208,7 @@ class ShopifyResource(ActiveResource, mixins.Countable):
     @classmethod
     def find(cls, id_=None, from_=None, **kwargs):
         """Checks the resulting collection for pagination metadata."""
-
-        collection = super(ShopifyResource, cls).find(id_=id_, from_=from_,
-                                                      **kwargs)
-
-        # pyactiveresource currently sends all headers from the response with
-        # the collection.
-        if isinstance(collection, Collection) and \
-           "headers" in collection.metadata:
-            headers = collection.metadata["headers"]
-            if "Link" in headers:
-                pagination = cls._parse_pagination(headers["Link"])
-                return PaginatedCollection(collection, metadata={
-                    "pagination": pagination,
-                    "resource_class": cls
-                })
-
+        collection = super(ShopifyResource, cls).find(id_=id_, from_=from_, **kwargs)
+        if isinstance(collection, Collection) and "headers" in collection.metadata:
+            return PaginatedCollection(collection, metadata={"resource_class": cls})
         return collection
-
-    @classmethod
-    def _parse_pagination(cls, data):
-        """Parses a Link header into a dict for cursor-based pagination.
-
-        Args:
-            data: The Link header value as a string.
-        Returns:
-            A dict with rel names as keys and URLs as values.
-        """
-
-        # Example Link header:
-        # <https://xxx.shopify.com/admin/...>; rel="previous",
-        # <https://xxx.shopify.com/admin/...>; rel="next"
-
-        values = data.split(", ")
-
-        result = {}
-        for value in values:
-            link, rel = value.split("; ")
-            result[rel.split('"')[1]] = link[1:-1]
-
-        return result
diff --git a/shopify/collection.py b/shopify/collection.py
index 5fa4a68..a604b1a 100644
--- a/shopify/collection.py
+++ b/shopify/collection.py
@@ -26,11 +26,12 @@ class PaginatedCollection(Collection):
                 metadata = obj.metadata
             super(PaginatedCollection, self).__init__(obj, metadata=metadata)
         else:
-            super(PaginatedCollection, self).__init__(metadata=metadata or {},
-                                                      *args, **kwargs)
-        if not ("pagination" in self.metadata and "resource_class" in self.metadata):
-            raise AttributeError("Cursor-based pagination requires \"pagination\" and \"resource_class\" attributes in the metadata.")
+            super(PaginatedCollection, self).__init__(metadata=metadata or {}, *args, **kwargs)
 
+        if not ("resource_class" in self.metadata):
+            raise AttributeError("Cursor-based pagination requires a \"resource_class\" attribute in the metadata.")
+
+        self.metadata["pagination"] = self.__parse_pagination()
         self.next_page_url = self.metadata["pagination"].get('next', None)
         self.previous_page_url = self.metadata["pagination"].get('previous', None)
 
@@ -39,6 +40,16 @@ class PaginatedCollection(Collection):
         self._current_iter = None
         self._no_iter_next = kwargs.pop("no_iter_next", False)
 
+    def __parse_pagination(self):
+        if "headers" not in self.metadata or "Link" not in self.metadata["headers"]:
+            return {}
+        values = self.metadata["headers"]["Link"].split(", ")
+        result = {}
+        for value in values:
+            link, rel = value.split("; ")
+            result[rel.split('"')[1]] = link[1:-1]
+        return result
+
     def has_previous_page(self):
         """Returns true if the current page has any previous pages before it.
         """


Test patch:
diff --git a/test/pagination_test.py b/test/pagination_test.py
index 461e8e0..3ee05ad 100644
--- a/test/pagination_test.py
+++ b/test/pagination_test.py
@@ -28,6 +28,13 @@ class PaginationTest(TestCase):
                   body=json.dumps({ "products": fixture[:2] }),
                   response_headers=next_headers)
 
+    def test_nonpaginates_collection(self):
+        self.fake('draft_orders', method='GET', code=200, body=self.load_fixture('draft_orders'))
+        draft_orders = shopify.DraftOrder.find()
+        self.assertEqual(1, len(draft_orders))
+        self.assertEqual(517119332, draft_orders[0].id)
+        self.assertIsInstance(draft_orders, shopify.collection.PaginatedCollection, "find() result is not PaginatedCollection")
+
     def test_paginated_collection(self):
         items = shopify.Product.find(limit=2)
         self.assertIsInstance(items, shopify.collection.PaginatedCollection, "find() result is not PaginatedCollection")