99
1010import sys
1111
12+ from .exceptions import ApiError , VkToolsException
1213from .execute import VkFunction
1314
1415
@@ -30,7 +31,7 @@ def __init__(self, vk):
3031 self .vk = vk
3132
3233 def get_all_iter (self , method , max_count , values = None , key = 'items' ,
33- limit = None , stop_fn = None ):
34+ limit = None , stop_fn = None , negative_offset = False ):
3435 """ Получить все элементы.
3536 Работает в методах, где в ответе есть count и items или users.
3637 За один запрос получает max_count * 25 элементов
@@ -54,30 +55,36 @@ def get_all_iter(self, method, max_count, values=None, key='items',
5455
5556 :param stop_fn: функция, отвечающая за выход из цикла
5657 :type stop_fn: func
58+
59+ :param negative_offset: True если offset должен быть отрицательный
60+ :type negative_offset: bool
5761 """
5862
5963 values = values .copy () if values else {}
64+ values ['count' ] = max_count
6065
61- items_count = 0
6266 offset = 0
67+ items_count = 0
68+ count = None
6369
6470 while True :
65- response = vk_get_all_items (
66- self .vk , method , values , key , max_count , offset
67- )
68-
69- items = response .get ('items' )
70- offset = response .get ('offset' )
71-
72- if items is None or response .get ('count' ) is None :
73- break # Error
74-
71+ try :
72+ response = vk_get_all_items (
73+ self .vk , method , key , values , count , offset ,
74+ offset_mul = - 1 if negative_offset else 1
75+ )
76+ except ApiError :
77+ raise VkToolsException (
78+ 'Can\' t load items. Check access to requested items'
79+ )
80+
81+ items = response ["items" ]
7582 items_count += len (items )
7683
7784 for item in items :
7885 yield item
7986
80- if offset >= response ['count ' ]:
87+ if not response ['more ' ]:
8188 break
8289
8390 if limit and items_count >= limit :
@@ -86,8 +93,11 @@ def get_all_iter(self, method, max_count, values=None, key='items',
8693 if stop_fn and stop_fn (items ):
8794 break
8895
96+ count = response ['count' ]
97+ offset = response ['offset' ]
98+
8999 def get_all (self , method , max_count , values = None , key = 'items' , limit = None ,
90- stop_fn = None ):
100+ stop_fn = None , negative_offset = False ):
91101 """ Использовать только если нужно загрузить все объекты в память.
92102
93103 Eсли вы можете обрабатывать объекты по частям, то лучше
@@ -97,13 +107,16 @@ def get_all(self, method, max_count, values=None, key='items', limit=None,
97107 все данные в память
98108 """
99109
100- items = list (self .get_all_iter (method , max_count , values , key , limit ,
101- stop_fn ))
110+ items = list (
111+ self .get_all_iter (
112+ method , max_count , values , key , limit , stop_fn , negative_offset
113+ )
114+ )
102115
103116 return {'count' : len (items ), key : items }
104117
105118 def get_all_slow_iter (self , method , max_count , values = None , key = 'items' ,
106- limit = None , stop_fn = None ):
119+ limit = None , stop_fn = None , negative_offset = False ):
107120 """ Получить все элементы (без использования execute)
108121 Работает в методах, где в ответе есть count и items или users
109122
@@ -126,34 +139,55 @@ def get_all_slow_iter(self, method, max_count, values=None, key='items',
126139
127140 :param stop_fn: функция, отвечающая за выход из цикла
128141 :type stop_fn: func
142+
143+ :param negative_offset: True если offset должен быть отрицательный
144+ :type negative_offset: bool
129145 """
130146
131147 values = values .copy () if values else {}
132-
133148 values ['count' ] = max_count
134149
135- response = self .vk .method (method , values )
136- count = response ['count' ]
137- items_count = 0
150+ offset_mul = - 1 if negative_offset else 1
138151
139- for offset in range ( max_count , count + 1 , max_count ):
140- values [ 'offset' ] = offset
152+ offset = max_count if negative_offset else 0
153+ count = None
141154
155+ items_count = 0
156+
157+ while count is None or offset < count :
158+ values ['offset' ] = offset * offset_mul
142159 response = self .vk .method (method , values )
143- items = response [key ]
160+
161+ new_count = response ['count' ]
162+
163+ count_diff = (new_count - count ) if count is not None else 0
164+
165+ if count_diff < 0 :
166+ offset += count_diff
167+ count = new_count
168+ continue
169+
170+ response_items = response [key ]
171+ items = response_items [count_diff :]
144172 items_count += len (items )
145173
146174 for item in items :
147175 yield item
148176
177+ if len (response_items ) < max_count - count_diff :
178+ break
179+
149180 if limit and items_count >= limit :
150181 break
151182
152183 if stop_fn and stop_fn (items ):
153184 break
154185
186+ offset += max_count
187+ count = new_count
188+
155189 def get_all_slow (self , method , max_count , values = None , key = 'items' ,
156- limit = None , stop_fn = None ):
190+ limit = None , stop_fn = None , negative_offset = False ):
157191 """ Использовать только если нужно загрузить все объекты в память.
158192
159193 Eсли вы можете обрабатывать объекты по частям, то лучше
@@ -164,34 +198,54 @@ def get_all_slow(self, method, max_count, values=None, key='items',
164198 """
165199
166200 items = list (
167- self .get_all_slow_iter (method , max_count , values , key , limit ,
168- stop_fn )
201+ self .get_all_slow_iter (
202+ method , max_count , values , key , limit , stop_fn , negative_offset
203+ )
169204 )
170205 return {'count' : len (items ), key : items }
171206
172207
173208vk_get_all_items = VkFunction (
174- args = ('method' , 'values' , 'key ' , 'max_count ' , 'start_offset ' ),
175- clean_args = ('method' , 'max_count ' , 'start_offset ' ),
209+ args = ('method' , 'key' , ' values' , 'count ' , 'offset ' , 'offset_mul ' ),
210+ clean_args = ('method' , 'key ' , 'offset' , 'offset_mul ' ),
176211 code = '''
177- var max_count = %(max_count)s,
178- offset = %(start_offset)s,
179- key = %(key)s;
180-
181- var params = {count: max_count, offset: offset} + %(values)s;
182-
183- var r = API.%(method)s(params),
184- items = r[key],
185- i = 1;
186-
187- while(i < 25 && offset + max_count <= r.count) {
188- offset = offset + max_count;
189- params.offset = offset;
190-
191- items = items + API.%(method)s(params)[key];
192-
193- i = i + 1;
212+ var params = %(values)s,
213+ calls = 0,
214+ items = [],
215+ count = %(count)s,
216+ offset = %(offset)s,
217+ ri;
218+
219+ while(calls < 25) {
220+ calls = calls + 1;
221+
222+ params.offset = offset * %(offset_mul)s;
223+ var response = API.%(method)s(params),
224+ new_count = response.count,
225+ count_diff = (count == null ? 0 : new_count - count);
226+
227+ if (count_diff < 0) {
228+ offset = offset + count_diff;
229+ } else {
230+ ri = response.%(key)s;
231+ items = items + ri.slice(count_diff);
232+ offset = offset + params.count + count_diff;
233+ if (ri.length < params.count) {
234+ calls = 99;
235+ }
236+ }
237+
238+ count = new_count;
239+
240+ if (count != null && offset >= count) {
241+ calls = 99;
242+ }
194243 };
195244
196- return {count: r.count, items: items, offset: offset + max_count};
245+ return {
246+ count: count,
247+ items: items,
248+ offset: offset,
249+ more: calls != 99
250+ };
197251''' )
0 commit comments