Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix AttributeError by accessing meta field when return_type is dict #1861

Merged
merged 5 commits into from Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 27 additions & 7 deletions tweepy/asynchronous/pagination.py
Expand Up @@ -4,13 +4,23 @@

from math import inf

import requests

from tweepy.client import Response


class AsyncPaginator:
""":class:`AsyncPaginator` can be used to paginate for any
:class:`AsyncClient` methods that support pagination

.. versionadded:: 4.11

.. note::

When passing ``return_type=requests.Response`` to :class:`Client` for
pagination, payload of response will be deserialized implicitly to get
``meta`` attribute every requests, which may affect performance.

Parameters
----------
method
Expand Down Expand Up @@ -58,10 +68,8 @@ async def flatten(self, limit=inf):


class AsyncPaginationIterator:

def __init__(
self, method, *args, limit=inf, pagination_token=None, reverse=False,
**kwargs
self, method, *args, limit=inf, pagination_token=None, reverse=False, **kwargs
):
self.method = method
self.args = args
Expand Down Expand Up @@ -92,17 +100,29 @@ async def __anext__(self):

# 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"
"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")
if isinstance(response, Response):
meta = response.meta
elif isinstance(response, dict):
meta = response.get("meta", {})
elif isinstance(response, requests.Response):
meta = response.json().get("meta", {})
else:
raise NotImplementedError(
f"Unknown {type(response)} return type for {self.method}"
)

self.previous_token = meta.get("previous_token")
self.next_token = meta.get("next_token")
self.count += 1

return response
30 changes: 18 additions & 12 deletions tweepy/pagination.py
Expand Up @@ -2,7 +2,6 @@
# Copyright 2009-2022 Joshua Roesslein
# See LICENSE for details.

from typing import Union
from math import inf

import requests
Expand All @@ -16,6 +15,12 @@ class Paginator:

.. versionadded:: 4.0

.. note::

When passing ``return_type=requests.Response`` to :class:`Client` for
pagination, payload of response will be deserialized implicitly to get
``meta`` attribute every requests, which may affect performance.
lqhuang marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
method
Expand All @@ -35,8 +40,7 @@ def __iter__(self):
return PaginationIterator(self.method, *self.args, **self.kwargs)

def __reversed__(self):
return PaginationIterator(self.method, *self.args, reverse=True,
**self.kwargs)
return PaginationIterator(self.method, *self.args, reverse=True, **self.kwargs)

def flatten(self, limit=inf):
"""Flatten paginated data
Expand All @@ -50,8 +54,7 @@ def flatten(self, limit=inf):
return

count = 0
for response in PaginationIterator(self.method, *self.args,
**self.kwargs):
for response in PaginationIterator(self.method, *self.args, **self.kwargs):
if response.data is not None:
for data in response.data:
yield data
Expand All @@ -61,9 +64,9 @@ def flatten(self, limit=inf):


class PaginationIterator:

lqhuang marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, method, *args, limit=inf, pagination_token=None,
reverse=False, **kwargs):
def __init__(
self, method, *args, limit=inf, pagination_token=None, reverse=False, **kwargs
):
self.method = method
self.args = args
self.limit = limit
Expand All @@ -82,7 +85,7 @@ def __init__(self, method, *args, limit=inf, pagination_token=None,
def __iter__(self):
return self

def __next__(self) -> Union[Response, dict, requests.Response]:
def __next__(self):
if self.reverse:
pagination_token = self.previous_token
else:
Expand All @@ -93,8 +96,9 @@ def __next__(self) -> Union[Response, dict, requests.Response]:

# 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"
"search_all_tweets",
"search_recent_tweets",
"get_all_tweets_count",
):
self.kwargs["next_token"] = pagination_token
else:
Expand All @@ -109,7 +113,9 @@ def __next__(self) -> Union[Response, dict, requests.Response]:
elif isinstance(response, requests.Response):
meta = response.json().get("meta", {})
lqhuang marked this conversation as resolved.
Show resolved Hide resolved
else:
raise NotImplementedError("Unknown `response` type to parse `meta` field")
raise NotImplementedError(
f"Unknown {type(response)} return type for {self.method}"
)

self.previous_token = meta.get("previous_token")
self.next_token = meta.get("next_token")
Expand Down