# -*- coding: utf-8 -*-
"""
:authors: python273
:contact: https://vk.com/python273
:license: Apache License, Version 2.0, see LICENSE file
:copyright: (c) 2018 python273
"""
import sys
from collections import namedtuple
from .utils import sjson_dumps
from .execute import VkFunction
if sys.version_info.major == 2:
range = xrange
class VkRequestsPoolException(Exception):
pass
PoolRequest = namedtuple('PoolRequest', ['method', 'values', 'result'])
[docs]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('Got error while executing request')
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
[docs]class VkRequestsPool(object):
"""
Позволяет сделать несколько обращений к API за один запрос
за счет метода execute.
Служит как менеджер контекста: запросы к API добавляются в
открытый пул, и выполняются при его закрытии.
:param vk: Объект :class:`VkApi`
"""
__slots__ = ('vk', 'pool', 'one_param', 'execute_errors')
def __init__(self, vk):
self.vk = vk
self.pool = []
self.one_param = {}
self.execute_errors = []
def __enter__(self):
return self
def __exit__(self, *args, **kwargs):
if self.one_param:
self.execute_one_param()
else:
self.execute()
def get_execute_errors(self):
return self.execute_errors
[docs] def method(self, method, values=None):
"""
Добавляет запрос в пул. Невозможно использовать вместе с :func:`method_one_param`.
Возвращаемое значение будет содержать результат после закрытия пула.
:param method: метод
:type method: str
:param values: параметры
:type values: dict
:rtype: RequestResult
"""
if self.one_param:
raise RuntimeError('One param mode does not work with self.method')
if values is None:
values = {}
result = RequestResult()
self.pool.append(PoolRequest(method, values, result))
return result
[docs] def method_one_param(self, method, key, values, default_values=None):
"""
Использовать, если изменяется значение только одного параметра.
Невозможно использовать вместе с :func:`method`.
Возвращаемое значение будет содержать результат после закрытия пула.
: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: RequestResult
"""
if not self.one_param and self.pool:
raise RuntimeError('One param mode does not work with self.method')
if default_values is None:
default_values = {}
self.one_param = {
'method': method,
'default': default_values,
'key': key,
'return': RequestResult()
}
self.pool = values
return self.one_param['return']
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, one_method, value_list)
else:
response_raw = vk_many_methods(self.vk, cur_pool)
response = response_raw['response']
response_errors = response_raw.get('execute_errors')
if response_errors:
self.execute_errors += response_errors[:-1]
for x, response in enumerate(response):
if response is not False:
cur_pool[x].result.result = response
else:
cur_pool[x].result.error = True
def execute_one_param(self):
result = {}
method = self.one_param['method']
default = self.one_param['default']
key = self.one_param['key']
for i in range(0, len(self.pool), 25):
cur_pool = self.pool[i:i + 25]
response_raw = vk_one_param(self.vk, method, cur_pool, default, key)
response = response_raw['response']
response_errors = response_raw.get('execute_errors')
if response_errors:
self.execute_errors += response_errors[:-1]
for x, r in enumerate(response):
if r is not False:
result[cur_pool[x]] = r
self.one_param['return'].result = result
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;
''')
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;
''')
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)