@@ -797,9 +797,6 @@ def __init__(self, obj, session=None, threads=4):
797
797
self ._obj = obj
798
798
799
799
def __next__ (self ):
800
- return self .next ()
801
-
802
- def next (self ):
803
800
if self ._pool is None :
804
801
self ._pool = concurrent .futures .ThreadPoolExecutor (max_workers = self ._num_threads )
805
802
@@ -831,6 +828,8 @@ def __iter__(self):
831
828
# All Iterators are also Iterables
832
829
return self
833
830
831
+ next = __next__ # Python 2 compatible
832
+
834
833
def _request_async (self , start ):
835
834
"""Used by worker threads to retrieve next batch of items."""
836
835
@@ -847,6 +846,9 @@ def _request_async(self, start):
847
846
class PagedItemIterator :
848
847
"""Iterates through a collection that must be "paged" from the server.
849
848
849
+ Uses `PageIterator` to transparently download pages of items from the server
850
+ as needed.
851
+
850
852
Parameters
851
853
----------
852
854
obj : RestObj
@@ -862,78 +864,135 @@ class PagedItemIterator:
862
864
------
863
865
RestObj
864
866
867
+ Notes
868
+ -----
869
+ Value returned by len() is an approximate count of the items available. The actual
870
+ number of items returned may be greater than or less than this number.
871
+
872
+ See Also
873
+ --------
874
+ PageIterator
875
+
865
876
"""
866
877
def __init__ (self , obj , session = None , threads = 4 ):
867
878
# Iterates over whole pages of items
868
879
self ._pager = PageIterator (obj , session , threads )
869
880
881
+ # Store items from latest page that haven't been returned yet.
882
+ self ._cache = []
883
+
870
884
# Total number of items to iterate over
871
885
if 'count' in obj :
886
+ # NOTE: "count" may be an (over) estimate of the number of items available
887
+ # since some may be inaccessible due to user permissions & won't actually be returned.
872
888
self ._count = int (obj .count )
873
889
else :
874
890
self ._count = len (obj ['items' ])
875
891
876
- self ._cache = []
877
-
878
892
def __len__ (self ):
879
893
return self ._count
880
894
881
895
def __next__ (self ):
882
- return self .next ()
883
-
884
- def next (self ):
885
896
# Get next page of items if we're currently out
886
897
if not self ._cache :
887
898
self ._cache = next (self ._pager )
888
899
900
+ if len (self ._cache ) < self ._pager ._limit :
901
+ print ('oops' )
902
+ # number of items returned in page was less than expected
903
+ # might be last page, or items might have been filtered out by server.
904
+
889
905
# Return the next item
890
906
if self ._cache :
907
+ self ._count -= 1
891
908
return self ._cache .pop (0 )
892
909
893
- raise StopIteration
910
+ raise StopIteration ()
911
+ # Out of items and out of pages
912
+ # self._count = 0
913
+ # raise StopIteration
894
914
895
915
def __iter__ (self ):
896
916
return self
897
917
918
+ next = __next__ # Python 2 compatible
919
+
920
+
921
+ class PagedListIterator :
922
+ """Iterates over an instance of PagedList
923
+
924
+ Parameters
925
+ ----------
926
+ l : list-like
898
927
899
- class PagedListIterator ():
928
+ """
900
929
def __init__ (self , l ):
901
930
self .__list = l
902
931
self .__index = 0
903
932
904
933
def __next__ (self ):
905
- return self .next ()
906
-
907
- def next (self ):
908
934
if self .__index >= len (self .__list ):
909
935
raise StopIteration
910
936
911
- item = self .__list [self .__index ]
912
- self .__index += 1
913
- return item
937
+ try :
938
+ item = self .__list [self .__index ]
939
+ self .__index += 1
940
+ return item
941
+ except IndexError :
942
+ # Because PagedList length is approximate, iterating can result in
943
+ # indexing outside the array. Just stop the iteration if that occurs.
944
+ raise StopIteration
914
945
915
946
def __iter__ (self ):
916
947
return self
917
948
949
+ next = __next__ # Python 2 compatibility
950
+
918
951
919
952
class PagedList (list ):
920
- def __init__ (self , obj , ** kwargs ):
953
+ """List that dynamically loads items from the server.
954
+
955
+ Parameters
956
+ ----------
957
+ obj : RestObj
958
+ An instance of `RestObj` containing any initial items and a link to
959
+ retrieve additional items.
960
+ session : Session, optional
961
+ The `Session` instance to use for requesting additional items. Defaults
962
+ to current_session()
963
+ threads : int, optional
964
+ Number of threads allocated to loading additional items.
965
+
966
+ Notes
967
+ -----
968
+ Value returned by len() is an approximate count of the items available. The actual
969
+ length is not known until all items have been pulled from the server.
970
+
971
+ See Also
972
+ --------
973
+ PagedItemIterator
974
+
975
+ """
976
+ def __init__ (self , obj , session = None , threads = 4 ):
921
977
super (PagedList , self ).__init__ ()
922
- self ._pager = PagedItemIterator (obj , ** kwargs )
978
+ self ._pager = PagedItemIterator (obj , session = session , threads = threads )
923
979
924
- # Force caching of first page
925
- self .append (next (self ._pager ))
926
- items = self ._pager ._cache
927
- self .extend (items )
980
+ # Add the first page of items to the list
981
+ for _ in range (len (self ._pager ._cache )):
982
+ self .append (next (self ._pager ))
928
983
929
- # clear the list (py27 compatible)
930
- del self ._pager . _cache [:]
984
+ # Assume that server has more items available
985
+ self ._has_more = True
931
986
932
987
def __len__ (self ):
933
- return len (self ._pager )
988
+ if self ._has_more :
989
+ # Estimate the total length as items downloaded + items still on server
990
+ return super (PagedList , self ).__len__ () + len (self ._pager )
991
+ else :
992
+ # We've pulled everything from the server, so we have an exact length now.
993
+ return super (PagedList , self ).__len__ ()
934
994
935
995
def __iter__ (self ):
936
- # return super(PagedList, self).__iter__()
937
996
return PagedListIterator (self )
938
997
939
998
def __getslice__ (self , i , j ):
@@ -949,15 +1008,22 @@ def __getitem__(self, item):
949
1008
else :
950
1009
idx = int (item )
951
1010
1011
+ # Support negative indexing. Need to load items up to len() - idx.
1012
+ if idx < 0 :
1013
+ idx = len (self ) + idx
1014
+
952
1015
try :
1016
+ # Iterate through server-side pages until we've loaded
1017
+ # the item at the requested index.
953
1018
while super (PagedList , self ).__len__ () <= idx :
954
1019
n = next (self ._pager )
955
1020
self .append (n )
1021
+
956
1022
except StopIteration :
957
- # May cause if slice or index extends beyond array
958
- # Ignore and let List handle IndexErrors if necessary.
959
- pass
1023
+ # We've hit the end of the paging so the server has no more items to retrieve.
1024
+ self ._has_more = False
960
1025
1026
+ # Get the item from the list
961
1027
return super (PagedList , self ).__getitem__ (item )
962
1028
963
1029
def __str__ (self ):
0 commit comments