Skip to content

Commit c3b4f6f

Browse files
hdk5python273
authored andcommitted
Fixed/reworked VkTools (#121)
1 parent 7d2fae2 commit c3b4f6f

File tree

2 files changed

+105
-47
lines changed

2 files changed

+105
-47
lines changed

vk_api/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,7 @@ class VkAudioException(Exception):
155155

156156
class VkAudioUrlDecodeError(VkAudioException):
157157
pass
158+
159+
160+
class VkToolsException(VkApiError):
161+
pass

vk_api/tools.py

+101-47
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import sys
1111

12+
from .exceptions import ApiError, VkToolsException
1213
from .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

173208
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'),
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

Comments
 (0)