You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
7.4 KiB

2 months ago
# -*- coding: utf-8 -*-
"""
:authors: python273
:license: Apache License, Version 2.0, see LICENSE file
:copyright: (c) 2019 python273
"""
from collections import namedtuple
from .exceptions import VkRequestsPoolException
from .execute import VkFunction
from .utils import sjson_dumps
PoolRequest = namedtuple('PoolRequest', ['method', 'values', 'result'])
class RequestResult(object):
""" Результат запроса из пула """
__slots__ = ('_result', 'ready', '_error')
def __init__(self):
self._result = None
self.ready = False
self._error = False
@property
def error(self):
"""Ошибка, либо `False`, если запрос прошёл успешно."""
return self._error
@error.setter
def error(self, value):
self._error = value
self.ready = True
@property
def result(self):
"""Результат запроса, если он прошёл успешно."""
if not self.ready:
raise RuntimeError('Result is not available in `with` context')
if self._error:
raise VkRequestsPoolException(
self._error,
'Got error while executing request: [{}] {}'.format(
self.error['error_code'],
self.error['error_msg']
)
)
return self._result
@result.setter
def result(self, result):
self._result = result
self.ready = True
@property
def ok(self):
"""`True`, если результат запроса не содержит ошибок, иначе `False`"""
return self.ready and not self._error
class VkRequestsPool(object):
"""
Позволяет сделать несколько обращений к API за один запрос
за счет метода execute.
Варианты использованя:
- В качестве менеджера контекста: запросы к API добавляются в
открытый пул, и выполняются при его закрытии.
- В качестве объекта пула. запросы к API дабвляются по одному
в пул и выполняются все вместе при выполнении метода execute()
:param vk_session: Объект :class:`VkApi`
"""
__slots__ = ('vk_session', 'pool')
def __init__(self, vk_session):
self.vk_session = vk_session
self.pool = []
def __enter__(self):
return self
def __exit__(self, *args, **kwargs):
self.execute()
def method(self, method, values=None):
""" Добавляет запрос в пул.
Возвращаемое значение будет содержать результат после закрытия пула.
:param method: метод
:type method: str
:param values: параметры
:type values: dict
:rtype: RequestResult
"""
if values is None:
values = {}
result = RequestResult()
self.pool.append(PoolRequest(method, values, result))
return result
def execute(self):
"""
Выполняет все находящиеся в пуле запросы и отчищает пул.
Необходим для использования пула-объекта.
Для пула менеджера контекста вызывается автоматически.
"""
for i in range(0, len(self.pool), 25):
cur_pool = self.pool[i:i + 25]
one_method = check_one_method(cur_pool)
if one_method:
value_list = [i.values for i in cur_pool]
response_raw = vk_one_method(
self.vk_session, one_method, value_list
)
else:
response_raw = vk_many_methods(self.vk_session, cur_pool)
response = response_raw['response']
response_errors = response_raw.get('execute_errors', [])
response_errors_iter = iter(response_errors)
for x, current_response in enumerate(response):
current_result = cur_pool[x].result
if current_response is not False:
current_result.result = current_response
else:
current_result.error = next(response_errors_iter)
self.pool = []
def check_one_method(pool):
""" Возвращает True, если все запросы в пуле к одному методу """
if not pool:
return False
first_method = pool[0].method
if all(req.method == first_method for req in pool[1:]):
return first_method
return False
vk_one_method = VkFunction(
args=('method', 'values'),
clean_args=('method',),
return_raw=True,
code='''
var values = %(values)s,
i = 0,
result = [];
while(i < values.length) {
result.push(API.%(method)s(values[i]));
i = i + 1;
}
return result;
''')
def vk_many_methods(vk_session, pool):
requests = ','.join(
'API.{}({})'.format(i.method, sjson_dumps(i.values))
for i in pool
)
code = 'return [{}];'.format(requests)
return vk_session.method('execute', {'code': code}, raw=True)
def vk_request_one_param_pool(vk_session, method, key, values,
default_values=None):
""" Использовать, если изменяется значение только одного параметра.
Возвращаемое значение содержит tuple из dict с результатами и
dict с ошибками при выполнении
:param vk_session: объект VkApi
:type vk_session: vk_api.VkAPi
:param method: метод
:type method: str
:param default_values: одинаковые значения для запросов
:type default_values: dict
:param key: ключ изменяющегося параметра
:type key: str
:param values: список значений изменяющегося параметра (max: 25)
:type values: list
:rtype: (dict, dict)
"""
result = {}
errors = {}
if default_values is None:
default_values = {}
for i in range(0, len(values), 25):
current_values = values[i:i + 25]
response_raw = vk_one_param(
vk_session, method, current_values, default_values, key
)
response = response_raw['response']
response_errors = response_raw.get('execute_errors', [])
response_errors_iter = iter(response_errors)
for x, r in enumerate(response):
if r is not False:
result[current_values[x]] = r
else:
errors[current_values[x]] = next(response_errors_iter)
return result, errors
vk_one_param = VkFunction(
args=('method', 'values', 'default_values', 'key'),
clean_args=('method', 'key'),
return_raw=True,
code='''
var def_values = %(default_values)s,
values = %(values)s,
result = [],
i = 0;
while(i < values.length) {
def_values.%(key)s = values[i];
result.push(API.%(method)s(def_values));
i = i + 1;
}
return result;
''')