Skip to content

Commit

Permalink
Настройка размера батчей и внутреннего предела operating_time (#246)
Browse files Browse the repository at this point in the history
* Настройка размера батчей и внутреннего предела operating_time
Fixes #241

* Fixes
  • Loading branch information
leshchenko1979 authored Jul 23, 2024
1 parent 8e1fb6f commit 11664eb
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 17 deletions.
7 changes: 6 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Внутри объекта ведётся учёт скорости отправки запросов к серверу, поэтому важно, чтобы все запросы приложения в отношении одного аккаунта с одного IP-адреса отправлялись из одного экземпляра `Bitrix`.

### Метод ` __init__(self, webhook: str, token_func: Awaitable = None, verbose: bool = True, respect_velocity_policy: bool = True, request_pool_size: int = 50, requests_per_second: float = 2.0, ssl: bool = True, client: aiohttp.ClientSession = None):`
### Метод ` __init__(self, webhook: str, token_func: Awaitable = None, verbose: bool = True, respect_velocity_policy: bool = True, request_pool_size: int = 50, requests_per_second: float = 2.0, batch_size: int = 50, operating_time_limit: int = 480, ssl: bool = True, client: aiohttp.ClientSession = None):`
Создаёт клиента для доступа к Битрикс24.

#### Параметры
Expand All @@ -16,6 +16,11 @@
можно отправить на сервер без ожидания
- `requests_per_second: float = 2.0` - максимальная скорость запросов,
которая будет использоваться после переполнения пула
- `batch_size: int = 50` - максимальное количество запросов, которые
будут отправляться на сервер в одном батче
- `operating_time_limit: int = 480` - максимальное допустимое время отработки
запросов к одному методу REST API в секундах, допустимое за 10 минут,
после которого запросы будут замедляться
- `ssl: bool = True` - использовать ли проверку SSL-сертификата при HTTP-соединениях с сервером Битрикс.
- `client: aiohttp.ClientSession = None` - использовать для HTTP-вызовов клиента, инициализированного и настроенного пользователем.

Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,9 @@ import logging
logging.getLogger('fast_bitrix24').addHandler(logging.StreamHandler())
```

### Я хочу добавить несколько лидов списком, но получаю ошибку сервера.
Оберните вызов `call()` в `slow`:
### Я получаю ошибку сервера, когда сильно его нагружаю. Что делать?
#### Способ 1
Оберните обращения к Битриксу в `slow`:

```python
with bx.slow():
Expand All @@ -249,6 +250,19 @@ with bx.slow():

[См. подробнее](API.md#контекстный-менеджер-slowmaxconcurrentrequests-int--1) о `slow`.

#### Способ 2
При инициализации клиента поэкспериментируйте со значениями параметров, регулирующих нагрузку на сервер:

```python
bx = Bitrix(
webhook,
request_pool_size = 20, # по умолчанию - 50
requests_per_second = 1.0, # по умолчанию - 1.0
batch_size = 20, # по умолчанию - 50
operating_time_limit = 100, # по умолчанию - 480
)
```

### Я хочу вызвать `call()` только один раз, а не по списку.
Передавайте параметры запроса методу `call()`, он может делать как запросы по списку, так и единичный запрос:

Expand Down
9 changes: 9 additions & 0 deletions fast_bitrix24/bitrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def __init__(
respect_velocity_policy: bool = True,
request_pool_size: int = 50,
requests_per_second: float = 2.0,
batch_size: int = 50,
operating_time_limit: int = 480,
client: aiohttp.ClientSession = None,
ssl: bool = True,
):
Expand All @@ -53,6 +55,11 @@ def __init__(
можно отправить на сервер без ожидания
- `requests_per_second: float = 2.0` - максимальная скорость запросов,
которая будет использоваться после переполнения пула
- `batch_size: int = 50` - максимальное количество запросов, которые
будут отправляться на сервер в одном батче
- `operating_time_limit: int = 480` - максимальное допустимое время отработки
запросов к одному методу REST API в секундах, допустимое за 10 минут,
после которого запросы будут замедляться
- `ssl: bool = True` - использовать ли проверку SSL-сертификата
при HTTP-соединениях с сервером Битрикс.
- `client: aiohttp.ClientSession = None` - использовать для HTTP-вызовов
Expand All @@ -69,10 +76,12 @@ def __init__(
respect_velocity_policy=respect_velocity_policy,
request_pool_size=request_pool_size,
requests_per_second=requests_per_second,
operating_time_limit=operating_time_limit,
ssl=ssl,
client=client,
)
self.verbose = verbose
self.batch_size = batch_size

@log
async def get_all(self, method: str, params: dict = None) -> Union[list, dict]:
Expand Down
4 changes: 2 additions & 2 deletions fast_bitrix24/mult_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from tqdm.auto import tqdm

from .server_response import ServerResponseParser
from .srh import BITRIX_MAX_BATCH_SIZE, ServerRequestHandler
from .srh import ServerRequestHandler
from .utils import http_build_query


Expand Down Expand Up @@ -38,7 +38,7 @@ def generate_tasks(self):

batches = (
self.package_batch(chunk)
for chunk in chunked(self.item_list, BITRIX_MAX_BATCH_SIZE)
for chunk in chunked(self.item_list, self.bitrix.batch_size)
)

for batch in batches:
Expand Down
10 changes: 4 additions & 6 deletions fast_bitrix24/srh.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
from .logger import logger
from .utils import _url_valid

BITRIX_MAX_BATCH_SIZE = 50
BITRIX_MAX_CONCURRENT_REQUESTS = 50

BITRIX_MAX_REQUEST_RUNNING_TIME = 480
BITRIX_MEASUREMENT_PERIOD = 10 * 60

MAX_RETRIES = 10
Expand Down Expand Up @@ -50,9 +48,6 @@ class ServerRequestHandler:
Основная цель - вести учет количества запросов, которые можно передать
серверу Битрикс без получения ошибки `5XX`.
Используется как контекстный менеджер, оборачивающий несколько
последовательных запросов к серверу.
"""

def __init__(
Expand All @@ -62,6 +57,7 @@ def __init__(
respect_velocity_policy: bool,
request_pool_size: int,
requests_per_second: float,
operating_time_limit: int,
client,
ssl: bool = True,
):
Expand All @@ -78,6 +74,8 @@ def __init__(

self.active_runs = 0

self.operating_time_limit = operating_time_limit

# если пользователь при инициализации передал клиента со своими настройками,
# то будем использовать его клиента
self.client_provided_by_user = bool(client)
Expand Down Expand Up @@ -249,7 +247,7 @@ async def acquire(self, method: str):
if self.respect_velocity_policy:
if method not in self.method_throttlers:
self.method_throttlers[method] = SlidingWindowThrottler(
BITRIX_MAX_REQUEST_RUNNING_TIME, BITRIX_MEASUREMENT_PERIOD
self.operating_time_limit, BITRIX_MEASUREMENT_PERIOD
)

async with self.method_throttlers[method].acquire():
Expand Down
7 changes: 6 additions & 1 deletion tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

from typing import Dict, List, Union


class MockSRH(ServerRequestHandler):
def __init__(self, token_func, response: Union[Dict, List[Dict]]):
self.response = response if isinstance(response, list) else [response]
self.element_no = 0

super().__init__("https://google.com/path", token_func, False, 50, 2, None)
super().__init__("https://google.com/path", token_func, False, 50, 2, 480, None)

async def request_attempt(self, *args, **kwargs):
result = self.response[self.element_no]
Expand All @@ -28,25 +29,29 @@ def test_first_request():

raise AssertionError


@pytest.mark.skip(reason="TODO")
def test_auth_success():
# нужно проверить, что серверу передается токен, полученный от token_func

raise AssertionError


@pytest.mark.skip(reason="TODO")
def test_auth_failure():
# нужно проверить, что вызывается функция запроса токена, если сервер вернул ошибку токена

raise AssertionError


@pytest.mark.skip(reason="TODO")
def test_abort_on_multiple_failures():
# нужно проверить, что если token_func регулярно возвращает токен, который отвергается сервером,
# то запрос оборвется после MAX_RETRIES неудачных попыток

raise AssertionError


def test_expired_token(bx_dummy):
# нужно проверить, что вызывается функция запроса токена, если токен истек

Expand Down
2 changes: 1 addition & 1 deletion tests/test_server_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, response: Union[Dict, List[Dict]]):
self.response = response if isinstance(response, list) else [response]
self.element_no = -1

super().__init__("https://google.com/path", None, False, 50, 2, None)
super().__init__("https://google.com/path", None, False, 50, 2, 480, None)

async def single_request(self, *args, **kwargs):
self.element_no += 1
Expand Down
11 changes: 7 additions & 4 deletions tests/test_srh.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from fast_bitrix24.srh import ServerRequestHandler
import aiohttp


@pytest.mark.asyncio
async def test_request_attempt():
# Create a mock response
mock_response = Mock(spec=aiohttp.ClientResponse)
mock_response.status = 200
mock_response.json.return_value = {'time': {'operating': 1000}}
mock_response.json.return_value = {"time": {"operating": 1000}}

@contextlib.asynccontextmanager
async def mock_post(url, json, ssl):
Expand All @@ -18,11 +19,13 @@ async def mock_post(url, json, ssl):
mock_session = AsyncMock()
mock_session.post = mock_post

handler = ServerRequestHandler('https://google.com/webhook', None, True, 50, 2, mock_session)
handler = ServerRequestHandler(
"https://google.com/webhook", None, True, 50, 2, 480, mock_session
)

# Call the method
result = await handler.request_attempt('method', {'param': 'value'})
result = await handler.request_attempt("method", {"param": "value"})

# Assert the expected behavior
assert result == {'time': {'operating': 1000}}
assert result == {"time": {"operating": 1000}}
assert "method" in handler.method_throttlers

0 comments on commit 11664eb

Please sign in to comment.