Add pagination for AsyncClient
authorHarmon <Harmon758@gmail.com>
Mon, 10 Oct 2022 05:25:14 +0000 (00:25 -0500)
committerHarmon <Harmon758@gmail.com>
Mon, 10 Oct 2022 05:28:29 +0000 (00:28 -0500)
docs/v2_pagination.rst
tweepy/asynchronous/__init__.py
tweepy/asynchronous/pagination.py [new file with mode: 0644]

index 8b3db430442ced12ca7d7b599238f31f139346aa..f8d581396df4e0daeb091272b3bc5633eaadd23d 100644 (file)
@@ -24,3 +24,6 @@ Pagination
     for tweet in tweepy.Paginator(client.search_recent_tweets, "Tweepy",
                                     max_results=100).flatten(limit=250):
         print(tweet.id)
+
+.. autoclass:: tweepy.asynchronous.AsyncPaginator
+    :members:
index d5dc1a8d0418b33bbb197c0c34a375e296633e57..3bd4dc2e46e7cf9a412df8045909ad97f9b26184 100644 (file)
@@ -21,3 +21,4 @@ except ModuleNotFoundError:
 
 from tweepy.asynchronous.streaming import AsyncStream, AsyncStreamingClient
 from tweepy.asynchronous.client import AsyncClient
+from tweepy.asynchronous.pagination import AsyncPaginator
diff --git a/tweepy/asynchronous/pagination.py b/tweepy/asynchronous/pagination.py
new file mode 100644 (file)
index 0000000..caf7cfa
--- /dev/null
@@ -0,0 +1,108 @@
+# Tweepy
+# Copyright 2009-2022 Joshua Roesslein
+# See LICENSE for details.
+
+from math import inf
+
+
+class AsyncPaginator:
+    """:class:`AsyncPaginator` can be used to paginate for any
+    :class:`AsyncClient` methods that support pagination
+
+    .. versionadded:: 4.11
+
+    Parameters
+    ----------
+    method
+        :class:`AsyncClient` method to paginate for
+    args
+        Positional arguments to pass to ``method``
+    kwargs
+        Keyword arguments to pass to ``method``
+    """
+
+    def __init__(self, method, *args, **kwargs):
+        self.method = method
+        self.args = args
+        self.kwargs = kwargs
+
+    def __aiter__(self):
+        return AsyncPaginationIterator(self.method, *self.args, **self.kwargs)
+
+    def __reversed__(self):
+        return AsyncPaginationIterator(
+            self.method, *self.args, reverse=True, **self.kwargs
+        )
+
+    async def flatten(self, limit=inf):
+        """Flatten paginated data
+
+        Parameters
+        ----------
+        limit
+            Maximum number of results to yield
+        """
+        if limit <= 0:
+            return
+
+        count = 0
+        async for response in AsyncPaginationIterator(
+            self.method, *self.args, **self.kwargs
+        ):
+            if response.data is not None:
+                for data in response.data:
+                    yield data
+                    count += 1
+                    if count == limit:
+                        return
+
+
+class AsyncPaginationIterator:
+
+    def __init__(
+        self, method, *args, limit=inf, pagination_token=None, reverse=False,
+        **kwargs
+    ):
+        self.method = method
+        self.args = args
+        self.limit = limit
+        self.kwargs = kwargs
+        self.reverse = reverse
+
+        if reverse:
+            self.previous_token = pagination_token
+            self.next_token = None
+        else:
+            self.previous_token = None
+            self.next_token = pagination_token
+
+        self.count = 0
+
+    def __aiter__(self):
+        return self
+
+    async def __anext__(self):
+        if self.reverse:
+            pagination_token = self.previous_token
+        else:
+            pagination_token = self.next_token
+
+        if self.count >= self.limit or self.count and pagination_token is None:
+            raise StopAsyncIteration
+
+        # https://twittercommunity.com/t/why-does-timeline-use-pagination-token-while-search-uses-next-token/150963
+        if self.method.__name__ in (
+            "search_all_tweets", "search_recent_tweets",
+            "get_all_tweets_count"
+        ):
+            self.kwargs["next_token"] = pagination_token
+        else:
+            self.kwargs["pagination_token"] = pagination_token
+
+        response = await self.method(*self.args, **self.kwargs)
+
+        self.previous_token = response.meta.get("previous_token")
+        self.next_token = response.meta.get("next_token")
+        self.count += 1
+
+        return response