9
9
10
10
import sys
11
11
12
+ from .exceptions import ApiError , VkToolsException
12
13
from .execute import VkFunction
13
14
14
15
@@ -30,7 +31,7 @@ def __init__(self, vk):
30
31
self .vk = vk
31
32
32
33
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 ):
34
35
""" Получить все элементы.
35
36
Работает в методах, где в ответе есть count и items или users.
36
37
За один запрос получает max_count * 25 элементов
@@ -54,30 +55,36 @@ def get_all_iter(self, method, max_count, values=None, key='items',
54
55
55
56
:param stop_fn: функция, отвечающая за выход из цикла
56
57
:type stop_fn: func
58
+
59
+ :param negative_offset: True если offset должен быть отрицательный
60
+ :type negative_offset: bool
57
61
"""
58
62
59
63
values = values .copy () if values else {}
64
+ values ['count' ] = max_count
60
65
61
- items_count = 0
62
66
offset = 0
67
+ items_count = 0
68
+ count = None
63
69
64
70
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" ]
75
82
items_count += len (items )
76
83
77
84
for item in items :
78
85
yield item
79
86
80
- if offset >= response ['count ' ]:
87
+ if not response ['more ' ]:
81
88
break
82
89
83
90
if limit and items_count >= limit :
@@ -86,8 +93,11 @@ def get_all_iter(self, method, max_count, values=None, key='items',
86
93
if stop_fn and stop_fn (items ):
87
94
break
88
95
96
+ count = response ['count' ]
97
+ offset = response ['offset' ]
98
+
89
99
def get_all (self , method , max_count , values = None , key = 'items' , limit = None ,
90
- stop_fn = None ):
100
+ stop_fn = None , negative_offset = False ):
91
101
""" Использовать только если нужно загрузить все объекты в память.
92
102
93
103
Eсли вы можете обрабатывать объекты по частям, то лучше
@@ -97,13 +107,16 @@ def get_all(self, method, max_count, values=None, key='items', limit=None,
97
107
все данные в память
98
108
"""
99
109
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
+ )
102
115
103
116
return {'count' : len (items ), key : items }
104
117
105
118
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 ):
107
120
""" Получить все элементы (без использования execute)
108
121
Работает в методах, где в ответе есть count и items или users
109
122
@@ -126,34 +139,55 @@ def get_all_slow_iter(self, method, max_count, values=None, key='items',
126
139
127
140
:param stop_fn: функция, отвечающая за выход из цикла
128
141
:type stop_fn: func
142
+
143
+ :param negative_offset: True если offset должен быть отрицательный
144
+ :type negative_offset: bool
129
145
"""
130
146
131
147
values = values .copy () if values else {}
132
-
133
148
values ['count' ] = max_count
134
149
135
- response = self .vk .method (method , values )
136
- count = response ['count' ]
137
- items_count = 0
150
+ offset_mul = - 1 if negative_offset else 1
138
151
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
141
154
155
+ items_count = 0
156
+
157
+ while count is None or offset < count :
158
+ values ['offset' ] = offset * offset_mul
142
159
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 :]
144
172
items_count += len (items )
145
173
146
174
for item in items :
147
175
yield item
148
176
177
+ if len (response_items ) < max_count - count_diff :
178
+ break
179
+
149
180
if limit and items_count >= limit :
150
181
break
151
182
152
183
if stop_fn and stop_fn (items ):
153
184
break
154
185
186
+ offset += max_count
187
+ count = new_count
188
+
155
189
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 ):
157
191
""" Использовать только если нужно загрузить все объекты в память.
158
192
159
193
Eсли вы можете обрабатывать объекты по частям, то лучше
@@ -164,34 +198,54 @@ def get_all_slow(self, method, max_count, values=None, key='items',
164
198
"""
165
199
166
200
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
+ )
169
204
)
170
205
return {'count' : len (items ), key : items }
171
206
172
207
173
208
vk_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 ' ),
176
211
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
+ }
194
243
};
195
244
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
+ };
197
251
''' )
0 commit comments