cursor.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. # Tweepy
  2. # Copyright 2009-2010 Joshua Roesslein
  3. # See LICENSE for details.
  4. from __future__ import print_function
  5. from tweepy.error import TweepError
  6. from tweepy.parsers import ModelParser, RawParser
  7. class Cursor(object):
  8. """Pagination helper class"""
  9. def __init__(self, method, *args, **kargs):
  10. if hasattr(method, 'pagination_mode'):
  11. if method.pagination_mode == 'cursor':
  12. self.iterator = CursorIterator(method, args, kargs)
  13. elif method.pagination_mode == 'id':
  14. self.iterator = IdIterator(method, args, kargs)
  15. elif method.pagination_mode == 'page':
  16. self.iterator = PageIterator(method, args, kargs)
  17. else:
  18. raise TweepError('Invalid pagination mode.')
  19. else:
  20. raise TweepError('This method does not perform pagination')
  21. def pages(self, limit=0):
  22. """Return iterator for pages"""
  23. if limit > 0:
  24. self.iterator.limit = limit
  25. return self.iterator
  26. def items(self, limit=0):
  27. """Return iterator for items in each page"""
  28. i = ItemIterator(self.iterator)
  29. i.limit = limit
  30. return i
  31. class BaseIterator(object):
  32. def __init__(self, method, args, kargs):
  33. self.method = method
  34. self.args = args
  35. self.kargs = kargs
  36. self.limit = 0
  37. def __next__(self):
  38. return self.next()
  39. def next(self):
  40. raise NotImplementedError
  41. def prev(self):
  42. raise NotImplementedError
  43. def __iter__(self):
  44. return self
  45. class CursorIterator(BaseIterator):
  46. def __init__(self, method, args, kargs):
  47. BaseIterator.__init__(self, method, args, kargs)
  48. start_cursor = kargs.pop('cursor', None)
  49. self.next_cursor = start_cursor or -1
  50. self.prev_cursor = start_cursor or 0
  51. self.num_tweets = 0
  52. def next(self):
  53. if self.next_cursor == 0 or (self.limit and self.num_tweets == self.limit):
  54. raise StopIteration
  55. data, cursors = self.method(cursor=self.next_cursor,
  56. *self.args,
  57. **self.kargs)
  58. self.prev_cursor, self.next_cursor = cursors
  59. if len(data) == 0:
  60. raise StopIteration
  61. self.num_tweets += 1
  62. return data
  63. def prev(self):
  64. if self.prev_cursor == 0:
  65. raise TweepError('Can not page back more, at first page')
  66. data, self.next_cursor, self.prev_cursor = self.method(cursor=self.prev_cursor,
  67. *self.args,
  68. **self.kargs)
  69. self.num_tweets -= 1
  70. return data
  71. class IdIterator(BaseIterator):
  72. def __init__(self, method, args, kargs):
  73. BaseIterator.__init__(self, method, args, kargs)
  74. self.max_id = kargs.pop('max_id', None)
  75. self.num_tweets = 0
  76. self.results = []
  77. self.model_results = []
  78. self.index = 0
  79. def next(self):
  80. """Fetch a set of items with IDs less than current set."""
  81. if self.limit and self.limit == self.num_tweets:
  82. raise StopIteration
  83. if self.index >= len(self.results) - 1:
  84. data = self.method(max_id=self.max_id, parser=RawParser(), *self.args, **self.kargs)
  85. if hasattr(self.method, '__self__'):
  86. old_parser = self.method.__self__.parser
  87. # Hack for models which expect ModelParser to be set
  88. self.method.__self__.parser = ModelParser()
  89. # This is a special invocation that returns the underlying
  90. # APIMethod class
  91. model = ModelParser().parse(self.method(create=True), data)
  92. if hasattr(self.method, '__self__'):
  93. self.method.__self__.parser = old_parser
  94. result = self.method.__self__.parser.parse(self.method(create=True), data)
  95. else:
  96. result = model
  97. if len(self.results) != 0:
  98. self.index += 1
  99. self.results.append(result)
  100. self.model_results.append(model)
  101. else:
  102. self.index += 1
  103. result = self.results[self.index]
  104. model = self.model_results[self.index]
  105. if len(result) == 0:
  106. raise StopIteration
  107. # TODO: Make this not dependant on the parser making max_id and
  108. # since_id available
  109. self.max_id = model.max_id
  110. self.num_tweets += 1
  111. return result
  112. def prev(self):
  113. """Fetch a set of items with IDs greater than current set."""
  114. if self.limit and self.limit == self.num_tweets:
  115. raise StopIteration
  116. self.index -= 1
  117. if self.index < 0:
  118. # There's no way to fetch a set of tweets directly 'above' the
  119. # current set
  120. raise StopIteration
  121. data = self.results[self.index]
  122. self.max_id = self.model_results[self.index].max_id
  123. self.num_tweets += 1
  124. return data
  125. class PageIterator(BaseIterator):
  126. def __init__(self, method, args, kargs):
  127. BaseIterator.__init__(self, method, args, kargs)
  128. self.current_page = 0
  129. def next(self):
  130. if self.limit > 0:
  131. if self.current_page > self.limit:
  132. raise StopIteration
  133. items = self.method(page=self.current_page, *self.args, **self.kargs)
  134. if len(items) == 0:
  135. raise StopIteration
  136. self.current_page += 1
  137. return items
  138. def prev(self):
  139. if self.current_page == 1:
  140. raise TweepError('Can not page back more, at first page')
  141. self.current_page -= 1
  142. return self.method(page=self.current_page, *self.args, **self.kargs)
  143. class ItemIterator(BaseIterator):
  144. def __init__(self, page_iterator):
  145. self.page_iterator = page_iterator
  146. self.limit = 0
  147. self.current_page = None
  148. self.page_index = -1
  149. self.num_tweets = 0
  150. def next(self):
  151. if self.limit > 0:
  152. if self.num_tweets == self.limit:
  153. raise StopIteration
  154. if self.current_page is None or self.page_index == len(self.current_page) - 1:
  155. # Reached end of current page, get the next page...
  156. self.current_page = self.page_iterator.next()
  157. self.page_index = -1
  158. self.page_index += 1
  159. self.num_tweets += 1
  160. return self.current_page[self.page_index]
  161. def prev(self):
  162. if self.current_page is None:
  163. raise TweepError('Can not go back more, at first page')
  164. if self.page_index == 0:
  165. # At the beginning of the current page, move to next...
  166. self.current_page = self.page_iterator.prev()
  167. self.page_index = len(self.current_page)
  168. if self.page_index == 0:
  169. raise TweepError('No more items')
  170. self.page_index -= 1
  171. self.num_tweets -= 1
  172. return self.current_page[self.page_index]